From 67c8e56f9cdad5c3289a36c734c800e53222881a Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 10 Sep 2019 18:20:29 +0200 Subject: [PATCH] QGIS Server WFS3 ACL + alias + excluded fields Takes into account aliases, excluded attributes and ACL plugins --- .../auto_generated/qgsserverapiutils.sip.in | 8 - src/server/qgsserverapiutils.cpp | 6 - src/server/qgsserverapiutils.h | 8 - src/server/qgsserverogcapihandler.cpp | 1 + src/server/services/wfs3/qgswfs3handlers.cpp | 145 ++++++++++-------- src/server/services/wfs3/qgswfs3handlers.h | 20 ++- tests/src/python/test_qgsserver_api.py | 19 ++- .../api/test_wfs3_api_project.json | 21 +-- .../api/test_wfs3_collections_empty.json | 2 +- ...collections_items_exclude_attribute_0.json | 32 ++++ ...ems_layer1_with_short_name_eq_tw_star.json | 40 +++++ ...s_items_layer1_with_short_name_eq_two.json | 40 +++++ ...testlayer_with_short_name_eq_two_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.json | 2 +- .../api/test_wfs3_conformance.json | 2 +- .../api/test_wfs3_landing_page.json | 2 +- 22 files changed, 240 insertions(+), 123 deletions(-) create mode 100644 tests/testdata/qgis_server/api/test_wfs3_collections_items_exclude_attribute_0.json create mode 100644 tests/testdata/qgis_server/api/test_wfs3_collections_items_layer1_with_short_name_eq_tw_star.json create mode 100644 tests/testdata/qgis_server/api/test_wfs3_collections_items_layer1_with_short_name_eq_two.json delete mode 100644 tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_with_short_name_eq_two_star.json diff --git a/python/server/auto_generated/qgsserverapiutils.sip.in b/python/server/auto_generated/qgsserverapiutils.sip.in index 01a007b3212..d9dbf2743ac 100644 --- a/python/server/auto_generated/qgsserverapiutils.sip.in +++ b/python/server/auto_generated/qgsserverapiutils.sip.in @@ -39,14 +39,6 @@ Parses a comma separated ``bbox`` into a (possibily empty) :py:class:`QgsRectang static QgsCoordinateReferenceSystem parseCrs( const QString &bboxCrs ); %Docstring Parses the CRS URI ``bboxCrs`` (example: "http://www.opengis.net/def/crs/OGC/1.3/CRS84") into a QGIS CRS object -%End - - static const QgsFields publishedFields( const QgsVectorLayer *layer ); -%Docstring -Returns the list of fields accessible to the service for a given ``layer``. - -This method takes into account the ACL restrictions provided by QGIS Server Access Control plugins. -TODO: implement ACL %End static const QVector publishedWfsLayers( const QgsProject *project ); diff --git a/src/server/qgsserverapiutils.cpp b/src/server/qgsserverapiutils.cpp index 3acfab31ae0..0118ad643a4 100644 --- a/src/server/qgsserverapiutils.cpp +++ b/src/server/qgsserverapiutils.cpp @@ -91,12 +91,6 @@ QgsCoordinateReferenceSystem QgsServerApiUtils::parseCrs( const QString &bboxCrs } } -const QgsFields QgsServerApiUtils::publishedFields( const QgsVectorLayer *layer ) -{ - // TODO: implement plugin's ACL filtering - return layer->fields(); -} - const QVector QgsServerApiUtils::publishedWfsLayers( const QgsProject *project ) { const QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project ); diff --git a/src/server/qgsserverapiutils.h b/src/server/qgsserverapiutils.h index 1dbd18a0bca..6c22a571645 100644 --- a/src/server/qgsserverapiutils.h +++ b/src/server/qgsserverapiutils.h @@ -71,14 +71,6 @@ class SERVER_EXPORT QgsServerApiUtils */ static QgsCoordinateReferenceSystem parseCrs( const QString &bboxCrs ); - /** - * Returns the list of fields accessible to the service for a given \a layer. - * - * This method takes into account the ACL restrictions provided by QGIS Server Access Control plugins. - * TODO: implement ACL - */ - static const QgsFields publishedFields( const QgsVectorLayer *layer ); - /** * Returns the list of layers accessible to the service for a given \a project. * diff --git a/src/server/qgsserverogcapihandler.cpp b/src/server/qgsserverogcapihandler.cpp index 1c48d15e4f2..2a0469ad3cd 100644 --- a/src/server/qgsserverogcapihandler.cpp +++ b/src/server/qgsserverogcapihandler.cpp @@ -173,6 +173,7 @@ void QgsServerOgcApiHandler::jsonDump( json &data, const QgsServerApiContext &co QDateTime time { QDateTime::currentDateTime() }; time.setTimeSpec( Qt::TimeSpec::UTC ); data["timeStamp"] = time.toString( Qt::DateFormat::ISODate ).toStdString() ; + context.response()->setStatusCode( 200 ); context.response()->setHeader( QStringLiteral( "Content-Type" ), contentType ); #ifdef QGISDEBUG context.response()->write( data.dump( 2 ) ); diff --git a/src/server/services/wfs3/qgswfs3handlers.cpp b/src/server/services/wfs3/qgswfs3handlers.cpp index 83d742bac9f..4f3fe776c59 100644 --- a/src/server/services/wfs3/qgswfs3handlers.cpp +++ b/src/server/services/wfs3/qgswfs3handlers.cpp @@ -33,6 +33,7 @@ #ifdef HAVE_SERVER_PYTHON_PLUGINS #include "qgsfilterrestorer.h" +#include "qgsaccesscontrol.h" #endif #include @@ -202,69 +203,71 @@ void QgsWfs3AbstractItemsHandler::checkLayerIsAccessible( const QgsVectorLayer * } } -QgsFeatureRequest QgsWfs3AbstractItemsHandler::filteredRequest( const QgsMapLayer *layer, const QgsServerApiContext &context ) const +QgsFeatureRequest QgsWfs3AbstractItemsHandler::filteredRequest( const QgsVectorLayer *vLayer, const QgsServerApiContext &context ) const { QgsFeatureRequest featureRequest; QgsExpressionContext expressionContext; expressionContext << QgsExpressionContextUtils::globalScope() << QgsExpressionContextUtils::projectScope( context.project() ) - << QgsExpressionContextUtils::layerScope( layer ); + << QgsExpressionContextUtils::layerScope( vLayer ); featureRequest.setExpressionContext( expressionContext ); - //is there alias info for this vector layer? - const QgsVectorLayer *vLayer = static_cast( layer ); - QMap< int, QString > layerAliasInfo; - const QgsStringMap aliasMap = vLayer->attributeAliases(); - for ( const auto &aliasKey : aliasMap.keys() ) - { - int attrIndex = vLayer->fields().lookupField( aliasKey ); - if ( attrIndex != -1 ) - { - layerAliasInfo.insert( attrIndex, aliasMap.value( aliasKey ) ); - } - } - - QgsAttributeList attrIndexes = vLayer->attributeList(); - - // Removed attributes - //excluded attributes for this layer - const QSet &layerExcludedAttributes = vLayer->excludeAttributesWfs(); - if ( !attrIndexes.isEmpty() && !layerExcludedAttributes.isEmpty() ) - { - const QgsFields &fields = vLayer->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( vLayer, featureRequest ); - - QStringList attributes = QStringList(); - for ( int idx : attrIndexes ) - { - attributes.append( vLayer->fields().field( idx ).name() ); - } - featureRequest.setSubsetOfAttributes( - accessControl->layerAttributes( vLayer, attributes ), - vLayer->fields() ); } #endif + QSet publishedAttrs; + const QgsFields constFields { publishedFields( vLayer, context ) }; + for ( const QgsField &f : constFields ) + { + publishedAttrs.insert( f.name() ); + } + featureRequest.setSubsetOfAttributes( publishedAttrs, vLayer->fields() ); return featureRequest; } +QgsFields QgsWfs3AbstractItemsHandler::publishedFields( const QgsVectorLayer *vLayer, const QgsServerApiContext &context ) const +{ + + QStringList publishedAttributes = QStringList(); + // Removed attributes + // WFS excluded attributes for this layer + const QSet &layerExcludedAttributes = vLayer->excludeAttributesWfs(); + const QgsFields &fields = vLayer->fields(); + for ( int i = 0; i < fields.count(); ++i ) + { + if ( ! layerExcludedAttributes.contains( fields.at( i ).name() ) ) + { + publishedAttributes.push_back( fields.at( i ).name() ); + } + } + +#ifdef HAVE_SERVER_PYTHON_PLUGINS + // Python plugins can make further modifications to the allowed attributes + QgsAccessControl *accessControl = context.serverInterface()->accessControls(); + if ( accessControl ) + { + publishedAttributes = accessControl->layerAttributes( vLayer, publishedAttributes ); + } +#endif + + QgsFields publishedFields; + for ( int i = 0; i < fields.count(); ++i ) + { + if ( publishedAttributes.contains( fields.at( i ).name() ) ) + { + publishedFields.append( fields.at( i ) ); + } + } + return publishedFields; +} + QgsWfs3LandingPageHandler::QgsWfs3LandingPageHandler() { } @@ -765,7 +768,8 @@ QList QgsWfs3CollectionsItemsHandler::parameters( } ); offset.setDescription( QStringLiteral( "Offset for features to retrieve [0-%1]" ).arg( mapLayer->featureCount( ) ) ); offsetValidatorSet = true; - for ( const auto &p : fieldParameters( mapLayer ) ) + const QList constFieldParameters { fieldParameters( mapLayer, context ) }; + for ( const auto &p : constFieldParameters ) { params.push_back( p ); } @@ -845,7 +849,8 @@ json QgsWfs3CollectionsItemsHandler::schema( const QgsServerApiContext &context } }; - for ( const auto &p : fieldParameters( mapLayer ) ) + const QList constFieldParameters { fieldParameters( mapLayer, context ) }; + for ( const auto &p : constFieldParameters ) { const std::string name { p.name().toStdString() }; parameters.push_back( p.data() ); @@ -899,14 +904,15 @@ json QgsWfs3CollectionsItemsHandler::schema( const QgsServerApiContext &context return data; } -const QList QgsWfs3CollectionsItemsHandler::fieldParameters( const QgsVectorLayer *mapLayer ) const +const QList QgsWfs3CollectionsItemsHandler::fieldParameters( const QgsVectorLayer *mapLayer, const QgsServerApiContext &context ) const { QList params; if ( mapLayer ) { - const QgsFields constFields { QgsServerApiUtils::publishedFields( mapLayer ) }; + const QgsFields constFields { publishedFields( mapLayer, context ) }; for ( const auto &f : constFields ) { + const QString fName { f.alias().isEmpty() ? f.name() : f.alias() }; QgsServerQueryStringParameter::Type t; switch ( f.type() ) { @@ -922,8 +928,8 @@ const QList QgsWfs3CollectionsItemsHandler::field t = QgsServerQueryStringParameter::Type::String; break; } - QgsServerQueryStringParameter fieldParam { f.name(), false, - t, QStringLiteral( "Retrieve features filtered by: %1 (%2)" ).arg( f.name() ) + QgsServerQueryStringParameter fieldParam { fName, false, + t, QStringLiteral( "Retrieve features filtered by: %1 (%2)" ).arg( fName ) .arg( QgsServerQueryStringParameter::typeName( t ) ) }; params.push_back( fieldParam ); } @@ -987,10 +993,11 @@ void QgsWfs3CollectionsItemsHandler::handleRequest( const QgsServerApiContext &c // Attribute filters QgsStringMap attrFilters; - const QgsFields constField { QgsServerApiUtils::publishedFields( mapLayer ) }; - for ( const QgsField &f : constField ) + const QgsFields constFields { publishedFields( mapLayer, context ) }; + for ( const QgsField &f : constFields ) { - const QString val = params.value( f.name() ).toString() ; + const QString fName { f.alias().isEmpty() ? f.name() : f.alias() }; + const QString val = params.value( fName ).toString() ; if ( ! val.isEmpty() ) { QString sanitized { QgsServerApiUtils::sanitizedFieldValue( val ) }; @@ -998,7 +1005,7 @@ void QgsWfs3CollectionsItemsHandler::handleRequest( const QgsServerApiContext &c { throw QgsServerApiBadRequestException( QStringLiteral( "Invalid filter field value [%1=%2]" ).arg( f.name() ).arg( val ) ); } - attrFilters[f.name()] = sanitized; + attrFilters[fName] = sanitized; } } @@ -1018,18 +1025,22 @@ void QgsWfs3CollectionsItemsHandler::handleRequest( const QgsServerApiContext &c } // Inputs are valid, process request - QgsFeatureRequest req; + QgsFeatureRequest featureRequest = filteredRequest( mapLayer, context ); if ( ! filterRect.isNull() ) { QgsCoordinateTransform ct( bboxCrs, mapLayer->crs(), context.project()->transformContext() ); ct.transform( filterRect ); - req.setFilterRect( ct.transform( filterRect ) ); + featureRequest.setFilterRect( ct.transform( filterRect ) ); } QString filterExpression; if ( ! attrFilters.isEmpty() ) { QStringList expressions; + if ( featureRequest.filterExpression() && ! featureRequest.filterExpression()->expression().isEmpty() ) + { + expressions.push_back( featureRequest.filterExpression()->expression() ); + } for ( auto it = attrFilters.constBegin(); it != attrFilters.constEnd(); it++ ) { // Handle star @@ -1045,17 +1056,19 @@ void QgsWfs3CollectionsItemsHandler::handleRequest( const QgsServerApiContext &c } } filterExpression = expressions.join( QStringLiteral( " AND " ) ); - req.setFilterExpression( filterExpression ); + featureRequest.setFilterExpression( filterExpression ); } // WFS3 core specs only serves 4326 - req.setDestinationCrs( crs, context.project()->transformContext() ); - // Add offset to limit because paging is not supported from QgsFeatureRequest - req.setLimit( limit + offset ); + featureRequest.setDestinationCrs( crs, context.project()->transformContext() ); + // Add offset to limit because paging is not supported by QgsFeatureRequest + featureRequest.setLimit( limit + offset ); QgsJsonExporter exporter { mapLayer }; + exporter.setAttributes( featureRequest.subsetOfAttributes() ); + exporter.setAttributeDisplayName( true ); exporter.setSourceCrs( mapLayer->crs() ); QgsFeatureList featureList; - QgsFeatureIterator features { mapLayer->getFeatures( req ) }; + QgsFeatureIterator features { mapLayer->getFeatures( featureRequest ) }; QgsFeature feat; long i { 0 }; while ( features.nextFeature( feat ) ) @@ -1076,11 +1089,11 @@ void QgsWfs3CollectionsItemsHandler::handleRequest( const QgsServerApiContext &c { if ( filterExpression.isEmpty() ) { - req.setNoAttributes(); + featureRequest.setNoAttributes(); } - req.setFlags( QgsFeatureRequest::Flag::NoGeometry ); - req.setLimit( -1 ); - features = mapLayer->getFeatures( req ); + featureRequest.setFlags( QgsFeatureRequest::Flag::NoGeometry ); + featureRequest.setLimit( -1 ); + features = mapLayer->getFeatures( featureRequest ); while ( features.nextFeature( feat ) ) { matchedFeaturesCount++; @@ -1191,7 +1204,6 @@ void QgsWfs3CollectionsFeatureHandler::handleRequest( const QgsServerApiContext if ( context.request()->method() == QgsServerRequest::Method::GetMethod ) { const QString featureId { match.captured( QStringLiteral( "featureId" ) ) }; - QgsJsonExporter exporter { mapLayer }; #ifdef HAVE_SERVER_PYTHON_PLUGINS QgsAccessControl *accessControl = context.serverInterface()->accessControls(); @@ -1213,6 +1225,9 @@ void QgsWfs3CollectionsFeatureHandler::handleRequest( const QgsServerApiContext QgsServerApiInternalServerError( QStringLiteral( "Invalid feature [%1]" ).arg( featureId ) ); } + QgsJsonExporter exporter { mapLayer }; + exporter.setAttributes( featureRequest.subsetOfAttributes() ); + exporter.setAttributeDisplayName( true ); json data = exporter.exportFeatureToJsonObject( feature ); data["links"] = links( context ); json navigation = json::array(); diff --git a/src/server/services/wfs3/qgswfs3handlers.h b/src/server/services/wfs3/qgswfs3handlers.h index ae9778b1b05..95aa2c51c57 100644 --- a/src/server/services/wfs3/qgswfs3handlers.h +++ b/src/server/services/wfs3/qgswfs3handlers.h @@ -19,6 +19,7 @@ #define QGS_WFS3_HANDLERS_H #include "qgsserverogcapihandler.h" +#include "qgsfields.h" class QgsFeatureRequest; class QgsServerOgcApi; @@ -42,7 +43,22 @@ class QgsWfs3AbstractItemsHandler: public QgsServerOgcApiHandler */ void checkLayerIsAccessible( const QgsVectorLayer *layer, const QgsServerApiContext &context ) const; - QgsFeatureRequest filteredRequest( const QgsMapLayer *layer, const QgsServerApiContext &context ) const; + /** + * Creates a filtered QgsFeatureRequest containing only fields published for WMS and plugin filters applied. + * \param layer the vector layer + * \param context the server api context + * \return QgsFeatureRequest with filters applied + */ + QgsFeatureRequest filteredRequest( const QgsVectorLayer *layer, const QgsServerApiContext &context ) const; + + /** + * Returns a filtered list of fields containing only fields published for WMS and plugin filters applied. + * @param layer the vector layer + * @param context the server api context + * @return QgsFields list with filters applied + */ + QgsFields publishedFields( const QgsVectorLayer *layer, const QgsServerApiContext &context ) const; + }; /** @@ -224,7 +240,7 @@ class QgsWfs3CollectionsItemsHandler: public QgsWfs3AbstractItemsHandler private: // Retrieve the fields filter parameters - const QList fieldParameters( const QgsVectorLayer *mapLayer ) const; + const QList fieldParameters( const QgsVectorLayer *mapLayer, const QgsServerApiContext &context ) const; }; diff --git a/tests/src/python/test_qgsserver_api.py b/tests/src/python/test_qgsserver_api.py index f11cefee989..26af2dd805a 100644 --- a/tests/src/python/test_qgsserver_api.py +++ b/tests/src/python/test_qgsserver_api.py @@ -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 = True + regeregenerate_api_reference = False def dump(self, response): """Returns the response body as str""" @@ -417,17 +417,26 @@ class QgsServerAPITest(QgsServerAPITestBase): request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer3/items?name=two') 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') + request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/layer1_with_short_name/items?name=two') self.server.handleRequest(request, response, project) self.assertEqual(response.statusCode(), 200) - self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_with_short_name_eq_two.json') + self.compareApi(request, project, 'test_wfs3_collections_items_layer1_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_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_two_star.json') + request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/layer1_with_short_name/items?name=tw*') + response = self.compareApi(request, project, 'test_wfs3_collections_items_layer1_with_short_name_eq_tw_star.json') + self.assertEqual(response.statusCode(), 200) + + def test_wfs3_excluded_attributes(self): + """Test excluded attributes""" + project = QgsProject() + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') + request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/exclude_attribute/items/0.geojson') + response = self.compareApi(request, project, 'test_wfs3_collections_items_exclude_attribute_0.json') + self.assertEqual(response.statusCode(), 200) class Handler1(QgsServerOgcApiHandler): 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 f9c4ecb1e03..6d68a86216b 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_api_project.json +++ b/tests/testdata/qgis_server/api/test_wfs3_api_project.json @@ -617,17 +617,6 @@ Content-Type: application/openapi+json;version=3.0 }, "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, @@ -704,10 +693,10 @@ Content-Type: application/openapi+json;version=3.0 } ], { - "description": "Filter the collection by 'id'", + "description": "Filter the collection by 'alias_id'", "explode": false, "in": "query", - "name": "id", + "name": "alias_id", "required": false, "schema": { "type": "integer" @@ -715,10 +704,10 @@ Content-Type: application/openapi+json;version=3.0 "style": "form" }, { - "description": "Filter the collection by 'name'", + "description": "Filter the collection by 'alias_name'", "explode": false, "in": "query", - "name": "name", + "name": "alias_name", "required": false, "schema": { "type": "string" @@ -1438,5 +1427,5 @@ Content-Type: application/openapi+json;version=3.0 "name": "Features" } ], - "timeStamp": "2019-09-08T18:57:09Z" + "timeStamp": "2019-09-10T18:18:14Z" } \ 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 cc481a399ca..11178ae6f1e 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-09-08T18:57:10Z" + "timeStamp": "2019-09-10T18:18:15Z" } \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_items_exclude_attribute_0.json b/tests/testdata/qgis_server/api/test_wfs3_collections_items_exclude_attribute_0.json new file mode 100644 index 00000000000..17fcb387f90 --- /dev/null +++ b/tests/testdata/qgis_server/api/test_wfs3_collections_items_exclude_attribute_0.json @@ -0,0 +1,32 @@ +Content-Type: application/geo+json + +{ + "geometry": { + "coordinates": [ + 8.203496, + 44.901483 + ], + "type": "Point" + }, + "id": 0, + "links": [ + { + "href": "http://server.qgis.org/wfs3/collections/exclude_attribute/items/0.geojson", + "rel": "self", + "title": "Retrieve a feature as GEOJSON", + "type": "application/geo+json" + }, + { + "href": "http://server.qgis.org/wfs3/collections/exclude_attribute/items/0.html", + "rel": "alternate", + "title": "Retrieve a feature as HTML", + "type": "text/html" + } + ], + "properties": { + "id": 1, + "utf8nameè": "one èé" + }, + "timeStamp": "2019-09-10T18:18:16Z", + "type": "Feature" +} \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_items_layer1_with_short_name_eq_tw_star.json b/tests/testdata/qgis_server/api/test_wfs3_collections_items_layer1_with_short_name_eq_tw_star.json new file mode 100644 index 00000000000..a4290674789 --- /dev/null +++ b/tests/testdata/qgis_server/api/test_wfs3_collections_items_layer1_with_short_name_eq_tw_star.json @@ -0,0 +1,40 @@ +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/layer1_with_short_name/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/layer1_with_short_name/items.html?name=tw*", + "rel": "alternate", + "title": "Retrieve the features of the collection as HTML", + "type": "text/html" + } + ], + "numberMatched": 1, + "numberReturned": 1, + "timeStamp": "2019-09-10T18:18:16Z", + "type": "FeatureCollection" +} \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_items_layer1_with_short_name_eq_two.json b/tests/testdata/qgis_server/api/test_wfs3_collections_items_layer1_with_short_name_eq_two.json new file mode 100644 index 00000000000..32606dc83f6 --- /dev/null +++ b/tests/testdata/qgis_server/api/test_wfs3_collections_items_layer1_with_short_name_eq_two.json @@ -0,0 +1,40 @@ +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/layer1_with_short_name/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/layer1_with_short_name/items.html?name=two", + "rel": "alternate", + "title": "Retrieve the features of the collection as HTML", + "type": "text/html" + } + ], + "numberMatched": 1, + "numberReturned": 1, + "timeStamp": "2019-09-10T18:18:16Z", + "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_two_star.json b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_with_short_name_eq_two_star.json deleted file mode 100644 index ec4bdf9e8ca..00000000000 --- a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_with_short_name_eq_two_star.json +++ /dev/null @@ -1,3 +0,0 @@ -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 7c64fbf2297..262cfab81da 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-09-08T18:57:09Z", + "timeStamp": "2019-09-10T18:18:14Z", "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 1fcf8507471..1ac27743b64 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-09-08T18:57:09Z", + "timeStamp": "2019-09-10T18:18:15Z", "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 49bf97865fd..789b90e2818 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-09-08T18:57:09Z", + "timeStamp": "2019-09-10T18:18:15Z", "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 0bb4370cfb9..5f5c0dfdc58 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-09-08T18:57:09Z", + "timeStamp": "2019-09-10T18:18:15Z", "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 a2acf2f3135..2dcb5b8970b 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-09-08T18:57:10Z", + "timeStamp": "2019-09-10T18:18:15Z", "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 fdea262c76d..d5fc96b0720 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-09-08T18:57:10Z", + "timeStamp": "2019-09-10T18:18:15Z", "type": "FeatureCollection" } \ No newline at end of file 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 70f8799bf61..678373ce287 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_collections_project.json +++ b/tests/testdata/qgis_server/api/test_wfs3_collections_project.json @@ -162,5 +162,5 @@ Content-Type: application/json "type": "text/html" } ], - "timeStamp": "2019-09-08T18:57:10Z" + "timeStamp": "2019-09-10T18:18:16Z" } \ 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 25eae55e904..173ee350d24 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-09-08T18:57:10Z" + "timeStamp": "2019-09-10T18:18:16Z" } \ 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 8abbf5a5173..11fb5e16148 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-09-08T18:57:11Z" + "timeStamp": "2019-09-10T18:18:17Z" } \ No newline at end of file