root/framework/server/trunk/mapfish/tests/test_postgis.py @ 3743

Revision 3743, 16.0 kB (checked in by elemoine, 2 years ago)

add tests to verify that Boolean columns/properties are dealt with correctly, no functional change

  • Property svn:mime-type set to text/plain
Line 
1#
2# Copyright (c) 2008-2011 Camptocamp.
3# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions
7# are met:
8# 1. Redistributions of source code must retain the above copyright
9#    notice, this list of conditions and the following disclaimer.
10# 2. Redistributions in binary form must reproduce the above copyright
11#    notice, this list of conditions and the following disclaimer in the
12#    documentation and/or other materials provided with the distribution.
13# 3. Neither the name of Camptocamp nor the names of its contributors may
14#    be used to endorse or promote products derived from this software
15#    without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
21# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27#
28
29""" This module includes unit tests for protocol.py using PostGIS as database"""
30
31import unittest
32from nose.tools import eq_, ok_, raises
33
34from sqlalchemy.ext.declarative import declarative_base
35from sqlalchemy import (create_engine, MetaData, Column, Integer, Numeric, Boolean)
36from sqlalchemy import orm
37
38from geoalchemy import (Point, Polygon, GeometryColumn, GeometryDDL)
39
40from mapfish.sqlalchemygeom import GeometryTableMixIn
41from mapfish.protocol import Protocol, create_geom_filter
42from test_protocol import FakeRequest, FakeResponse
43
44from webob.exc import HTTPNotFound
45from exceptions import Exception
46from geojson import Feature, FeatureCollection, GeoJSON
47
48
49engine = create_engine('postgresql://gis:gis@localhost/gis', echo=True)
50metadata = MetaData(engine)
51sm = orm.sessionmaker(autoflush=True, autocommit=False, bind=engine)
52session = orm.scoped_session(sm)
53
54
55Base = declarative_base(metadata=metadata)
56
57class Spot(Base, GeometryTableMixIn):
58    __tablename__ = 'spots'
59
60    spot_id = Column(Integer, primary_key=True)
61    spot_height = Column(Numeric(asdecimal=False))
62    spot_goodness = Column(Boolean)
63    spot_location = GeometryColumn(Point(2))
64
65GeometryDDL(Spot.__table__)
66
67class Lake(Base, GeometryTableMixIn):
68    __tablename__ = 'lakes'
69
70    id = Column(Integer, primary_key=True)
71    depth = Column(Numeric(asdecimal=False))
72    geom = GeometryColumn(Polygon(2))
73
74GeometryDDL(Lake.__table__)
75
76class Test(unittest.TestCase):
77
78
79    def setUp(self):
80 
81        metadata.drop_all()
82        metadata.create_all()
83
84        # Insert some points into the database
85        session.add_all([
86            Spot(spot_height=420.40, spot_location='POINT(0 0)', spot_goodness=True),
87            Spot(spot_height=102.34, spot_location='POINT(10 10)', spot_goodness=True),
88            Spot(spot_height=388.62, spot_location='POINT(10 11)', spot_goodness=True),
89            Spot(spot_height=1454.66, spot_location='POINT(40 34)', spot_goodness=True),
90            Spot(spot_height=54.66, spot_location='POINT(5 5)', spot_goodness=True),
91            Spot(spot_height=333.12, spot_location='POINT(2 3)', spot_goodness=True),
92            Spot(spot_height=783.55, spot_location='POINT(38 34)', spot_goodness=True),
93            Spot(spot_height=3454.67, spot_location='POINT(-134 45)', spot_goodness=True),
94            Spot(spot_height=6454.23, spot_location=None, spot_goodness=False)
95            ])
96       
97        session.commit()
98       
99
100    def tearDown(self):
101        session.rollback()
102        metadata.drop_all()
103       
104
105    def test_protocol_create(self):
106        """Create a new point"""
107        proto = Protocol(session, Spot)
108       
109        request = FakeRequest({})
110        request.body = '{"type": "FeatureCollection", "features": [{"type": "Feature", "properties": {"spot_height": 12.0, "spot_goodness": true}, "geometry": {"type": "Point", "coordinates": [45, 5]}}]}'
111       
112        response = FakeResponse()
113        collection = proto.create(request, response)
114        eq_(response.status, 201)
115        eq_(len(collection.features), 1)
116        feature0 = collection.features[0]
117        eq_(feature0.id, 10)
118        eq_(feature0.geometry.coordinates, (45.0, 5.0))
119        eq_(feature0.properties["spot_height"], 12)
120        eq_(feature0.properties["spot_goodness"], True)
121
122        new_spot = session.query(Spot).filter(Spot.spot_height==12.0).one()
123        ok_(new_spot is not None)
124        eq_(session.scalar(new_spot.spot_location.wkt), u'POINT(45 5)')
125        eq_(new_spot.spot_goodness, True)
126       
127
128    def test_protocol_create_and_update(self):
129        """Create a new point and also update an already existing point"""
130       
131        old_spot = session.query(Spot).filter(Spot.spot_height==102.34).one()
132       
133        proto = Protocol(session, Spot)
134       
135        request = FakeRequest({})
136        request.body = '{"type": "FeatureCollection", "features": [\
137            {"type": "Feature", "properties": {"spot_height": 12.0}, "geometry": {"type": "Point", "coordinates": [45, 5]}},\
138            {"type": "Feature", "id": ' + str(old_spot.spot_id) + ', "properties": {}, "geometry": {"type": "Point", "coordinates": [1, 1]}}]}'       
139       
140        response = FakeResponse()
141        collection = proto.create(request, response)
142        eq_(response.status, 201)
143        eq_(len(collection.features), 2)
144        feature0 = collection.features[0]
145        eq_(feature0.id, 10)
146        eq_(feature0.geometry.coordinates, (45.0, 5.0))
147        eq_(feature0.properties["spot_height"], 12)
148        feature1 = collection.features[1]
149        eq_(feature1.id, old_spot.spot_id)
150        eq_(feature1.geometry.coordinates, (1, 1))
151
152        new_spot = session.query(Spot).filter(Spot.spot_height==12.0).one()
153        ok_(new_spot is not None)
154        eq_(session.scalar(new_spot.spot_location.wkt), u'POINT(45 5)')
155       
156        updated_spot = session.query(Spot).filter(Spot.spot_height==102.34).one()
157        ok_(updated_spot is not None)
158        ok_(old_spot is updated_spot)
159        eq_(updated_spot.spot_height, 102.34)
160        eq_(session.scalar(updated_spot.spot_location.wkt), u'POINT(1 1)')
161       
162
163    @raises(Exception)
164    def test_protocol_create_fails(self):
165        """Try to create a feature without geometry"""
166        proto = Protocol(session, Spot)
167       
168        request = FakeRequest({})
169        request.body = '{"type": "FeatureCollection", "features": [{"type": "Feature", "properties": {"spot_height": 12.0}}]}'
170       
171        proto.create(request, FakeResponse())
172       
173
174    def test_protocol_update(self):
175        """Update an existing point"""
176        proto = Protocol(session, Spot)
177        id = 1
178       
179        request = FakeRequest({})
180        request.body = '{"type": "Feature", "id": ' + str(id) + ', "properties": {}, "geometry": {"type": "Point", "coordinates": [1, 1]}}'
181       
182        response = FakeResponse()
183        feature = proto.update(request, response, id)
184        eq_(response.status, 201)
185        eq_(feature.id, 1)
186        eq_(feature.geometry.coordinates, (1.0, 1.0))
187       
188        spot = session.query(Spot).get(id)
189        ok_(spot is not None)
190        eq_(session.scalar(spot.spot_location.wkt), u'POINT(1 1)')
191       
192       
193    @raises(HTTPNotFound)
194    def test_protocol_update_fails(self):
195        """Try to update a not-existing feature"""
196        proto = Protocol(session, Spot)
197        id = -1
198       
199        request = FakeRequest({})
200        request.body = '{"type": "Feature", "id": ' + str(id) + ', "properties": {}, "geometry": {"type": "Point", "coordinates": [1, 1]}}'
201       
202        response = FakeResponse()
203        proto.update(request, response, id)
204       
205
206    def test_protocol_delete(self):
207        """Delete an existing point"""
208        proto = Protocol(session, Spot)
209        id = 1
210       
211        request = FakeRequest({})
212        response = FakeResponse()
213       
214        proto.delete(request, response, id)
215        eq_(response.status, 204)
216       
217        spot = session.query(Spot).get(id)
218        ok_(spot is None)
219
220
221    @raises(HTTPNotFound)
222    def test_protocol_delete_fails(self):
223        """Try to delete a not-existing point"""
224        proto = Protocol(session, Spot)
225       
226        proto.delete(FakeRequest({}), FakeResponse(), -1)
227       
228
229    def test_protocol_count(self):
230        """Get the feature count"""
231        proto = Protocol(session, Spot)
232       
233        eq_(proto.count(FakeRequest({})), '9')
234       
235
236    def test_protocol_count_filter_box(self):
237        """Get the feature count with a box as filter"""
238        proto = Protocol(session, Spot)
239        request = FakeRequest({})
240       
241        request.params['bbox'] = '-10,-10,10,10'
242        eq_(proto.count(request), '4')
243       
244        request.params['tolerance'] = '1'
245        eq_(proto.count(request), '5')
246       
247        # reproject the bbox
248        request.params['bbox'] = '-12.3364241712925,-10.0036833569465,7.66304367998925,9.9979519038951'
249        request.params['epsg'] = '4807'
250        request.params['tolerance'] = '0'
251        eq_(proto.count(request), '4')
252       
253
254    def test_protocol_count_filter_within(self):
255        """Get the feature count with a point as filter"""
256        proto = Protocol(session, Spot)
257        request = FakeRequest({})
258       
259        request.params['lat'] = '0'
260        request.params['lon'] = '0'
261        eq_(proto.count(request), '1')
262       
263        request.params['tolerance'] = '10'
264        eq_(proto.count(request), '3')
265       
266
267    def test_protocol_count_filter_geometry(self):
268        """Get the feature count with a geometry as filter"""
269        proto = Protocol(session, Spot)
270        request = FakeRequest({})
271       
272        request.params['geometry'] = '{"type": "Polygon", "coordinates": [[ [-10, -1], [10, -1], [0, 10], [-10, -1] ]]}'
273        eq_(proto.count(request), '2')
274       
275        request.params['tolerance'] = '10'
276        eq_(proto.count(request), '5')
277       
278
279    def test_protocol_count_queryable(self):
280        """Count all features that match a filter"""
281        proto = Protocol(session, Spot)
282        request = FakeRequest({})
283        request.params['queryable'] = 'spot_height'
284        request.params['spot_height__gte'] = '1454.66'
285       
286        eq_(proto.count(request), '3')
287       
288       
289    def test_protocol_count_custom_filter(self):
290        """Count all features that match a custom filter"""
291        session.add_all([
292            Lake(depth=20, geom='POLYGON((-88.7968950764331 43.2305732929936,-88.7935511273885 43.1553344394904,-88.716640299363 43.1570064140127,-88.7250001719745 43.2339172420382,-88.7968950764331 43.2305732929936))'),
293            Lake(depth=5, geom='POLYGON((-88.1147292993631 42.7540605095542,-88.1548566878981 42.7824840764331,-88.1799363057325 42.7707802547771,-88.188296178344 42.7323248407643,-88.1832802547771 42.6955414012739,-88.1565286624204 42.6771496815287,-88.1448248407643 42.6336783439491,-88.131449044586 42.5718152866242,-88.1013535031847 42.565127388535,-88.1080414012739 42.5868630573248,-88.1164012738854 42.6119426751592,-88.1080414012739 42.6520700636943,-88.0980095541401 42.6838375796178,-88.0846337579618 42.7139331210191,-88.1013535031847 42.7423566878981,-88.1147292993631 42.7540605095542))'),
294            Lake(depth=120, geom='POLYGON((-89.0694267515924 43.1335987261147,-89.1078821656051 43.1135350318471,-89.1329617834395 43.0884554140127,-89.1312898089172 43.0466560509554,-89.112898089172 43.0132165605096,-89.0694267515924 42.9898089171975,-89.0343152866242 42.953025477707,-89.0209394904459 42.9179140127389,-89.0042197452229 42.8961783439491,-88.9774681528663 42.8644108280255,-88.9440286624204 42.8292993630573,-88.9072452229299 42.8142515923567,-88.8687898089172 42.815923566879,-88.8687898089172 42.815923566879,-88.8102707006369 42.8343152866242,-88.7734872611465 42.8710987261147,-88.7517515923567 42.9145700636943,-88.7433917197452 42.9730891719745,-88.7517515923567 43.0299363057325,-88.7734872611465 43.0867834394905,-88.7885352038217 43.158678388535,-88.8738057324841 43.1620222929936,-88.947372611465 43.1937898089172,-89.0042197452229 43.2138535031847,-89.0410031847134 43.2389331210191,-89.0710987261147 43.243949044586,-89.0660828025478 43.2238853503185,-89.0543789808917 43.203821656051,-89.0376592356688 43.175398089172,-89.0292993630573 43.1519904458599,-89.0376592356688 43.1369426751592,-89.0393312101911 43.1386146496815,-89.0393312101911 43.1386146496815,-89.0510350318471 43.1335987261147,-89.0694267515924 43.1335987261147))'),
295            Lake(depth=450, geom='POLYGON((-88.9122611464968 43.038296178344,-88.9222929936306 43.0399681528663,-88.9323248407643 43.0282643312102,-88.9206210191083 43.0182324840764,-88.9105891719745 43.0165605095542,-88.9005573248408 43.0232484076433,-88.9072452229299 43.0282643312102,-88.9122611464968 43.038296178344))')
296            ])
297        session.commit();
298       
299        proto = Protocol(session, Lake)
300       
301        from sqlalchemy.sql import and_
302
303        request = FakeRequest({})
304        request.params['bbox'] = '-90,40,-80,45'
305       
306        filter = create_geom_filter(request, Lake)
307
308        compare_filter = Lake.geom.area >= 0.1
309        filter = and_(filter, compare_filter)
310           
311        eq_(proto.count(request, filter=filter), '1')
312
313
314    def test_protocol_read_all(self):
315        """Return all features"""
316        proto = Protocol(session, Spot)
317
318        collection = proto.read(FakeRequest({}))
319        ok_(collection is not None)
320        ok_(isinstance(collection, FeatureCollection))
321        eq_(len(collection.features), 9)
322
323
324    def test_protocol_read_one(self):
325        """Return one feature"""
326        proto = Protocol(session, Spot)
327
328        feature = proto.read(FakeRequest({}), id=1)
329        ok_(feature is not None)
330        ok_(isinstance(feature, Feature))
331        eq_(feature.id, 1)
332        eq_(feature.geometry.coordinates, (0.0, 0.0))
333        eq_(feature.properties["spot_height"], 420.39999999999998)
334        eq_(feature.properties["spot_goodness"], True)
335
336
337    def test_protocol_read_one_null(self):
338        """Return one null feature"""
339        proto = Protocol(session, Spot)
340
341        feature = proto.read(FakeRequest({}), id=9)
342        ok_(feature is not None)
343        ok_(isinstance(feature, Feature))
344        eq_(feature.id, 9)
345        # make use of __geo_interface__ property since 'geometry'
346        # value is not the same in various versions of geojson lib
347        ok_(feature.__geo_interface__['geometry'] is None)
348        ok_(feature.__geo_interface__['bbox'] is None)
349
350
351    @raises(HTTPNotFound)
352    def test_protocol_read_one_fails(self):
353        """Try to get a single point with a wrong primary key"""
354        proto = Protocol(session, Spot)
355       
356        proto.read(FakeRequest({}), id=-1)
357
358
359    def test_within_distance(self):
360        """Test the PostGIS implementation for within_distance"""
361        from mapfish.sqlalchemygeom import within_distance
362       
363        eq_(session.scalar(within_distance('POINT(-88.9139332929936 42.5082802993631)',
364                                                               'POINT(-88.9139332929936 35.5082802993631)', 10)), True)
365        ok_(session.scalar(within_distance('Point(0 0)', 'Point(0 0)', 0)))
366        ok_(session.scalar(within_distance('Point(0 0)',
367                                           'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0)))
368        ok_(session.scalar(within_distance('Point(5 5)',
369                                           'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0)))
370        ok_(session.scalar(within_distance('Point(6 5)',
371                                           'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 1)))
372        ok_(session.scalar(within_distance('Polygon((0 0, 1 0, 1 8, 0 8, 0 0))',
373                                           'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0)))
Note: See TracBrowser for help on using the browser.