Skip to content

Commit 0469ffc

Browse files
committed
Merge pull request #3106 from boundlessgeo/wfs-t-tests-2
Wfs t tests
2 parents 0627ac3 + 6b715c5 commit 0469ffc

20 files changed

+886
-0
lines changed

src/providers/wfs/qgswfsprovider.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ void QgsWFSProviderSQLColumnRefValidator::visit( const QgsSQLStatement::NodeColu
271271

272272
bool QgsWFSProvider::processSQL( const QString& sqlString, QString& errorMsg )
273273
{
274+
QgsDebugMsg( QString( "Processing SQL: %1" ).arg( sqlString ) );
274275
errorMsg.clear();
275276
QgsSQLStatement sql( sqlString );
276277
if ( sql.hasParserError() )

tests/src/python/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,5 @@ ENDIF (WITH_APIDOC)
124124
IF (WITH_SERVER)
125125
ADD_PYTHON_TEST(PyQgsServer test_qgsserver.py)
126126
ADD_PYTHON_TEST(PyQgsServerAccessControl test_qgsserver_accesscontrol.py)
127+
ADD_PYTHON_TEST(PyQgsServerWFST test_qgsserver_wfst.py)
127128
ENDIF (WITH_SERVER)
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
QGIS Server HTTP wrapper
4+
5+
This script launches a QGIS Server listening on port 8081 or on the port
6+
specified on the environment variable QGIS_SERVER_DEFAULT_PORT
7+
8+
.. note:: This program is free software; you can redistribute it and/or modify
9+
it under the terms of the GNU General Public License as published by
10+
the Free Software Foundation; either version 2 of the License, or
11+
(at your option) any later version.
12+
"""
13+
14+
__author__ = 'Alessandro Pasotti'
15+
__date__ = '05/15/2016'
16+
__copyright__ = 'Copyright 2016, The QGIS Project'
17+
# This will get replaced with a git SHA1 when you do a git archive
18+
__revision__ = '$Format:%H$'
19+
20+
21+
import os
22+
import urlparse
23+
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
24+
from qgis.server import QgsServer
25+
26+
try:
27+
QGIS_SERVER_DEFAULT_PORT = os.environ('QGIS_SERVER_DEFAULT_PORT')
28+
except:
29+
QGIS_SERVER_DEFAULT_PORT = 8081
30+
31+
32+
class Handler(BaseHTTPRequestHandler):
33+
34+
def do_GET(self):
35+
parsed_path = urlparse.urlparse(self.path)
36+
s = QgsServer()
37+
headers, body = s.handleRequest(parsed_path.query)
38+
self.send_response(200)
39+
for k, v in [h.split(':') for h in headers.split('\n') if h]:
40+
self.send_header(k, v)
41+
self.end_headers()
42+
self.wfile.write(body)
43+
return
44+
45+
def do_POST(self):
46+
content_len = int(self.headers.getheader('content-length', 0))
47+
post_body = self.rfile.read(content_len)
48+
request = post_body[1:post_body.find(' ')]
49+
self.path = self.path + '&REQUEST_BODY=' + \
50+
post_body.replace('&', '') + '&REQUEST=' + request
51+
return self.do_GET()
52+
53+
54+
if __name__ == '__main__':
55+
server = HTTPServer(('localhost', QGIS_SERVER_DEFAULT_PORT), Handler)
56+
print 'Starting server on localhost:%s, use <Ctrl-C> to stop' % \
57+
QGIS_SERVER_DEFAULT_PORT
58+
server.serve_forever()
+289
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Tests for WFS-T provider using QGIS Server through qgis_wrapped_server.py.
4+
5+
This is an integration test for QGIS Desktop WFS-T provider and QGIS Server
6+
WFS-T that check if QGIS can talk to and uderstand itself.
7+
8+
The test uses testdata/wfs_transactional/wfs_transactional.qgs and three
9+
initially empty shapefiles layrs with points, lines and polygons.
10+
11+
All WFS-T calls are executed through the QGIS WFS data provider.
12+
13+
The three layers are
14+
15+
1. populated with WFS-T
16+
2. checked for geometry and attributes
17+
3. modified with WFS-T
18+
4. checked for geometry and attributes
19+
5. emptied with WFS-T calls to delete
20+
21+
22+
From build dir, run: ctest -R PyQgsServerWFST -V
23+
24+
.. note:: This program is free software; you can redistribute it and/or modify
25+
it under the terms of the GNU General Public License as published by
26+
the Free Software Foundation; either version 2 of the License, or
27+
(at your option) any later version.
28+
"""
29+
30+
__author__ = 'Alessandro Pasotti'
31+
__date__ = '05/15/2016'
32+
__copyright__ = 'Copyright 2016, The QGIS Project'
33+
# This will get replaced with a git SHA1 when you do a git archive
34+
__revision__ = '$Format:%H$'
35+
36+
37+
import os
38+
import subprocess
39+
from shutil import copytree, rmtree
40+
import tempfile
41+
from time import sleep
42+
from utilities import unitTestDataPath
43+
from qgis.core import (
44+
QgsVectorLayer,
45+
QgsFeature,
46+
QgsGeometry,
47+
QgsPoint,
48+
QgsRectangle,
49+
QgsFeatureRequest,
50+
QgsExpression,
51+
)
52+
from qgis.testing import (
53+
start_app,
54+
unittest,
55+
)
56+
57+
try:
58+
QGIS_SERVER_WFST_DEFAULT_PORT = os.environ['QGIS_SERVER_WFST_DEFAULT_PORT']
59+
except:
60+
QGIS_SERVER_WFST_DEFAULT_PORT = 8081
61+
62+
63+
qgis_app = start_app()
64+
65+
66+
class TestWFST(unittest.TestCase):
67+
68+
@classmethod
69+
def setUpClass(cls):
70+
"""Run before all tests"""
71+
cls.port = QGIS_SERVER_WFST_DEFAULT_PORT
72+
cls.testdata_path = unitTestDataPath('wfs_transactional') + '/'
73+
# Create tmp folder
74+
cls.temp_path = tempfile.mkdtemp()
75+
cls.testdata_path = cls.temp_path + '/' + 'wfs_transactional' + '/'
76+
copytree(unitTestDataPath('wfs_transactional') + '/',
77+
cls.temp_path + '/' + 'wfs_transactional')
78+
cls.project_path = cls.temp_path + '/' + 'wfs_transactional' + '/' + \
79+
'wfs_transactional.qgs'
80+
assert os.path.exists(cls.project_path), "Project not found: %s" % \
81+
cls.project_path
82+
# Clean env just to be sure
83+
env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
84+
for ev in env_vars:
85+
try:
86+
del os.environ[ev]
87+
except KeyError:
88+
pass
89+
# Clear all test layers
90+
for ln in ['test_point', 'test_polygon', 'test_linestring']:
91+
cls._clearLayer(ln)
92+
os.environ['QGIS_SERVER_DEFAULT_PORT'] = str(cls.port)
93+
server_path = os.path.dirname(os.path.realpath(__file__)) + \
94+
'/qgis_wrapped_server.py'
95+
cls.server = subprocess.Popen(['python', server_path],
96+
env=os.environ)
97+
sleep(2)
98+
99+
@classmethod
100+
def tearDownClass(cls):
101+
"""Run after all tests"""
102+
cls.server.terminate()
103+
del cls.server
104+
# Clear all test layers
105+
for ln in ['test_point', 'test_polygon', 'test_linestring']:
106+
layer = cls._getLayer(ln)
107+
cls._clearLayer(ln)
108+
rmtree(cls.temp_path)
109+
110+
def setUp(self):
111+
"""Run before each test."""
112+
pass
113+
114+
def tearDown(self):
115+
"""Run after each test."""
116+
pass
117+
118+
@classmethod
119+
def _clearLayer(cls, layer_name):
120+
"""
121+
Delete all features from a vector layer
122+
"""
123+
layer = cls._getLayer(layer_name)
124+
layer.startEditing()
125+
layer.deleteFeatures([f.id() for f in layer.getFeatures()])
126+
layer.commitChanges()
127+
assert layer.featureCount() == 0
128+
129+
@classmethod
130+
def _getLayer(cls, layer_name):
131+
"""
132+
OGR Layer factory
133+
"""
134+
path = cls.testdata_path + layer_name + '.shp'
135+
layer = QgsVectorLayer(path, layer_name, "ogr")
136+
assert layer.isValid()
137+
return layer
138+
139+
@classmethod
140+
def _getWFSLayer(cls, type_name, layer_name=None):
141+
"""
142+
WFS layer factory
143+
"""
144+
if layer_name is None:
145+
layer_name = 'wfs_' + type_name
146+
parms = {
147+
'srsname': 'EPSG:4326',
148+
'typename': type_name,
149+
'url': 'http://127.0.0.1:%s/?map=%s' % (cls.port,
150+
cls.project_path),
151+
'version': 'auto',
152+
'table': '',
153+
#'sql': '',
154+
}
155+
uri = ' '.join([("%s='%s'" % (k, v)) for k, v in parms.iteritems()])
156+
wfs_layer = QgsVectorLayer(uri, layer_name, 'WFS')
157+
assert wfs_layer.isValid()
158+
return wfs_layer
159+
160+
@classmethod
161+
def _getFeatureByAttribute(cls, layer, attr_name, attr_value):
162+
"""
163+
Find the feature and return it, raise exception if not found
164+
"""
165+
request = QgsFeatureRequest(QgsExpression("%s=%s" % (attr_name,
166+
attr_value)))
167+
try:
168+
return layer.dataProvider().getFeatures(request).next()
169+
except StopIteration:
170+
raise Exception("Wrong attributes in WFS layer %s" %
171+
layer.name())
172+
173+
def _checkAddFeatures(self, wfs_layer, layer, features):
174+
"""
175+
Check features were added
176+
"""
177+
wfs_layer.dataProvider().addFeatures(features)
178+
layer = self._getLayer(layer.name())
179+
self.assertTrue(layer.isValid())
180+
self.assertEqual(layer.featureCount(), len(features))
181+
182+
def _checkUpdateFeatures(self, wfs_layer, old_features, new_features):
183+
"""
184+
Check features can be updated
185+
"""
186+
for i in range(len(old_features)):
187+
f = self._getFeatureByAttribute(wfs_layer, 'id', old_features[i]['id'])
188+
self.assertTrue(wfs_layer.dataProvider().changeGeometryValues({f.id(): new_features[i].geometry()}))
189+
self.assertTrue(wfs_layer.dataProvider().changeAttributeValues({f.id(): {0: new_features[i]['id']}}))
190+
191+
def _checkMatchFeatures(self, wfs_layer, features):
192+
"""
193+
Check feature attributes and geometry match
194+
"""
195+
for f in features:
196+
wf = self._getFeatureByAttribute(wfs_layer, 'id', f['id'])
197+
self.assertEqual(wf.geometry().exportToWkt(),
198+
f.geometry().exportToWkt())
199+
200+
def _checkDeleteFeatures(self, layer, features):
201+
"""
202+
Delete features
203+
"""
204+
ids = []
205+
for f in features:
206+
wf = self._getFeatureByAttribute(layer, 'id', f['id'])
207+
ids.append(wf.id())
208+
self.assertTrue(layer.dataProvider().deleteFeatures(ids))
209+
210+
def _testLayer(self, wfs_layer, layer, old_features, new_features):
211+
"""
212+
Perform all test steps on the layer.
213+
"""
214+
self.assertEqual(wfs_layer.featureCount(), 0)
215+
self._checkAddFeatures(wfs_layer, layer, old_features)
216+
self._checkMatchFeatures(wfs_layer, old_features)
217+
self.assertEqual(wfs_layer.dataProvider().featureCount(),
218+
len(old_features))
219+
self._checkUpdateFeatures(wfs_layer, old_features, new_features)
220+
self._checkMatchFeatures(wfs_layer, new_features)
221+
self._checkDeleteFeatures(wfs_layer, new_features)
222+
self.assertEqual(wfs_layer.dataProvider().featureCount(), 0)
223+
224+
def testWFSPoints(self):
225+
"""
226+
Adds some points, then check and clear all
227+
"""
228+
layer_name = 'test_point'
229+
layer = self._getLayer(layer_name)
230+
wfs_layer = self._getWFSLayer(layer_name)
231+
feat1 = QgsFeature(wfs_layer.pendingFields())
232+
feat1['id'] = 11
233+
feat1.setGeometry(QgsGeometry.fromPoint(QgsPoint(9, 45)))
234+
feat2 = QgsFeature(wfs_layer.pendingFields())
235+
feat2.setGeometry(QgsGeometry.fromPoint(QgsPoint(9.5, 45.5)))
236+
feat2['id'] = 12
237+
old_features = [feat1, feat2]
238+
# Change feat1
239+
new_feat1 = QgsFeature(wfs_layer.pendingFields())
240+
new_feat1['id'] = 121
241+
new_feat1.setGeometry(QgsGeometry.fromPoint(QgsPoint(10, 46)))
242+
new_features = [new_feat1, feat2]
243+
self._testLayer(wfs_layer, layer, old_features, new_features)
244+
245+
def testWFSPolygons(self):
246+
"""
247+
Adds some polygons, then check and clear all
248+
"""
249+
layer_name = 'test_polygon'
250+
layer = self._getLayer(layer_name)
251+
wfs_layer = self._getWFSLayer(layer_name)
252+
feat1 = QgsFeature(wfs_layer.pendingFields())
253+
feat1['id'] = 11
254+
feat1.setGeometry(QgsGeometry.fromRect(QgsRectangle(QgsPoint(9, 45), QgsPoint(10, 46))))
255+
feat2 = QgsFeature(wfs_layer.pendingFields())
256+
feat2.setGeometry(QgsGeometry.fromRect(QgsRectangle(QgsPoint(9.5, 45.5), QgsPoint(10.5, 46.5))))
257+
feat2['id'] = 12
258+
old_features = [feat1, feat2]
259+
# Change feat1
260+
new_feat1 = QgsFeature(wfs_layer.pendingFields())
261+
new_feat1['id'] = 121
262+
new_feat1.setGeometry(QgsGeometry.fromRect(QgsRectangle(QgsPoint(10, 46), QgsPoint(11.5, 47.5))))
263+
new_features = [new_feat1, feat2]
264+
self._testLayer(wfs_layer, layer, old_features, new_features)
265+
266+
def testWFSLineStrings(self):
267+
"""
268+
Adds some lines, then check and clear all
269+
"""
270+
layer_name = 'test_linestring'
271+
layer = self._getLayer(layer_name)
272+
wfs_layer = self._getWFSLayer(layer_name)
273+
feat1 = QgsFeature(wfs_layer.pendingFields())
274+
feat1['id'] = 11
275+
feat1.setGeometry(QgsGeometry.fromPolyline([QgsPoint(9, 45), QgsPoint(10, 46)]))
276+
feat2 = QgsFeature(wfs_layer.pendingFields())
277+
feat2.setGeometry(QgsGeometry.fromPolyline([QgsPoint(9.5, 45.5), QgsPoint(10.5, 46.5)]))
278+
feat2['id'] = 12
279+
old_features = [feat1, feat2]
280+
# Change feat1
281+
new_feat1 = QgsFeature(wfs_layer.pendingFields())
282+
new_feat1['id'] = 121
283+
new_feat1.setGeometry(QgsGeometry.fromPolyline([QgsPoint(9.8, 45.8), QgsPoint(10.8, 46.8)]))
284+
new_features = [new_feat1, feat2]
285+
self._testLayer(wfs_layer, layer, old_features, new_features)
286+
287+
288+
if __name__ == '__main__':
289+
unittest.main()
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]
Binary file not shown.
Binary file not shown.
65 Bytes
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]
100 Bytes
Binary file not shown.
100 Bytes
Binary file not shown.
65 Bytes
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]
100 Bytes
Binary file not shown.
100 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)