Projet

Général

Profil

0001-generalize-ArcGIS-connector-17763.patch

Thomas Noël, 02 novembre 2018 18:50

Télécharger (30,3 ko)

Voir les différences:

Subject: [PATCH 1/2] generalize ArcGIS connector (#17763)

 .../migrations/0003_auto_20181102_1550.py     |  59 ++++
 passerelle/contrib/arcgis/models.py           | 198 +++++++++---
 .../templates/arcgis/arcgis_detail.html       |  24 --
 tests/test_arcgis.py                          | 300 +++++++++++++-----
 tests/test_arcgis_nancy.py                    |  82 +++++
 tests/test_generic_endpoint.py                |   4 +-
 6 files changed, 523 insertions(+), 144 deletions(-)
 create mode 100644 passerelle/contrib/arcgis/migrations/0003_auto_20181102_1550.py
 delete mode 100644 passerelle/contrib/arcgis/templates/arcgis/arcgis_detail.html
 create mode 100644 tests/test_arcgis_nancy.py
passerelle/contrib/arcgis/migrations/0003_auto_20181102_1550.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.16 on 2018-11-02 14:50
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6

  
7

  
8
class Migration(migrations.Migration):
9

  
10
    dependencies = [
11
        ('arcgis', '0002_auto_20170920_0951'),
12
    ]
13

  
14
    operations = [
15
        migrations.AlterModelOptions(
16
            name='arcgis',
17
            options={'verbose_name': 'ArcGIS REST API'},
18
        ),
19
        migrations.AddField(
20
            model_name='arcgis',
21
            name='basic_auth_password',
22
            field=models.CharField(blank=True, max_length=128, verbose_name='Basic authentication password'),
23
        ),
24
        migrations.AddField(
25
            model_name='arcgis',
26
            name='basic_auth_username',
27
            field=models.CharField(blank=True, max_length=128, verbose_name='Basic authentication username'),
28
        ),
29
        migrations.AddField(
30
            model_name='arcgis',
31
            name='client_certificate',
32
            field=models.FileField(blank=True, null=True, upload_to=b'', verbose_name='TLS client certificate'),
33
        ),
34
        migrations.AddField(
35
            model_name='arcgis',
36
            name='http_proxy',
37
            field=models.CharField(blank=True, max_length=128, verbose_name='HTTP and HTTPS proxy'),
38
        ),
39
        migrations.AddField(
40
            model_name='arcgis',
41
            name='trusted_certificate_authorities',
42
            field=models.FileField(blank=True, null=True, upload_to=b'', verbose_name='TLS trusted CAs'),
43
        ),
44
        migrations.AddField(
45
            model_name='arcgis',
46
            name='verify_cert',
47
            field=models.BooleanField(default=True, verbose_name='TLS verify certificates'),
48
        ),
49
        migrations.AlterField(
50
            model_name='arcgis',
51
            name='base_url',
52
            field=models.URLField(verbose_name='Webservice Base URL'),
53
        ),
54
        migrations.AlterField(
55
            model_name='arcgis',
56
            name='log_level',
57
            field=models.CharField(choices=[(b'NOTSET', b'NOTSET'), (b'DEBUG', b'DEBUG'), (b'INFO', b'INFO'), (b'WARNING', b'WARNING'), (b'ERROR', b'ERROR'), (b'CRITICAL', b'CRITICAL')], default=b'INFO', max_length=10, verbose_name='Log Level'),
58
        ),
59
    ]
passerelle/contrib/arcgis/models.py
1
# Copyright (C) 2016  Entr'ouvert
1
# passerelle - uniform access to multiple data sources and services
2
# Copyright (C) 2018 Entr'ouvert
2 3
#
3 4
# This program is free software: you can redistribute it and/or modify it
4 5
# under the terms of the GNU Affero General Public License as published
......
13 14
# You should have received a copy of the GNU Affero General Public License
14 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 16

  
17
import urlparse
18

  
16 19
from django.db import models
20
from django.template import Template, Context
17 21
from django.utils.translation import ugettext_lazy as _
18 22

  
19
from passerelle.base.models import BaseResource
20
from passerelle.utils.api import endpoint
21 23
from passerelle.utils.jsonresponse import APIError
24
from passerelle.utils.api import endpoint
25
from passerelle.base.models import BaseResource, HTTPResource
22 26

  
23 27

  
24
class ParameterTypeError(Exception):
25
    http_status = 400
26
    log_error = False
27

  
28
class ArcGISError(APIError):
29
    pass
28 30

  
29
class Arcgis(BaseResource):
30
    base_url = models.CharField(_('SIG Url'), max_length=256)
31 31

  
32
class ArcGIS(BaseResource, HTTPResource):
32 33
    category = _('Geographic information system')
33 34

  
35
    base_url = models.URLField(_('Webservice Base URL'))
36

  
34 37
    class Meta:
35
        verbose_name = _('Arcgis Webservice')
38
        verbose_name = _('ArcGIS REST API')
36 39

  
37
    @endpoint()
38
    def district(self, request, lon=None, lat=None):
39
        if lon and lat:
40
            try:
41
                lon, lat = float(lon), float(lat)
42
                geometry = '{}, {}'.format(lon, lat)
43
                geometryType = 'esriGeometryPoint'
44
            except(ValueError,):
45
                raise ParameterTypeError('<lon> and <lat> must be floats')
46
        else:
47
            geometry = ''
48
            geometryType = 'esriGeometryEnvelope'
40
    @endpoint(name='mapservice-query',
41
              description=_('Map Service Query'),
42
              perm='can_access',
43
              parameters={
44
                  'folder': {
45
                      'description': _('Folder name'),
46
                      'example_value': 'Specialty',
47
                  },
48
                  'service': {
49
                      'description': _('Service name'),
50
                      'example_value': 'ESRI_StateCityHighway_USA',
51
                  },
52
                  'layer': {
53
                      'description': _('Layer or table name'),
54
                      'example_value': '1',
55
                  },
56
                  'lat': {'description': _('Latitude')},
57
                  'lon': {'description': _('Longitude')},
58
                  'latmin': {'description': _('Minimal latitude (envelope)')},
59
                  'lonmin': {'description': _('Minimal longitude (envelope)')},
60
                  'latmax': {'description': _('Minimal atitude (envelope)')},
61
                  'lonmax': {'description': _('Minimal Longitude (envelope)')},
62
                  'q': {'description': _('Search text in display field')},
63
                  'template': {
64
                      'description': _('Django template for text attribute'),
65
                      'example_value': '{{ attributes.STATE_NAME }} ({{ attributes.STATE_ABBR }})',
66
                  },
67
                  'id_template': {
68
                      'description': _('Django template for id attribute'),
69
                  },
70
                  'full': {
71
                      'description': _('Returns all ArcGIS informations (geometry, metadata)'),
72
                      'type': 'bool',
73
                  },
74
              })
75
    def mapservice_query(self, request, service, layer='0', folder='', lat=None, lon=None,
76
                         latmin=None, lonmin=None, latmax=None, lonmax=None, q=None,
77
                         template=None, id_template=None, full=False, **kwargs):
78
        url = urlparse.urljoin(self.base_url, 'services/')
79
        if folder:
80
            url = urlparse.urljoin(url, folder + '/')
81
        url = urlparse.urljoin(url, service + '/MapServer/' + layer + '/query')
49 82

  
83
        # build query params
84
        # cf https://developers.arcgis.com/rest/services-reference/query-map-service-layer-.htm
50 85
        params = {
51
            'where': '1=1',
52
            'f': 'pjson',
53
            'geometry': geometry,
54
            'geometryType': geometryType,
86
            'f': 'json',
55 87
            'inSR': '4326',
56
            'outFields': '*',
57 88
            'outSR': '4326',
58
            'returnCountOnly': 'false',
59
            'returnDistinctValues': 'false',
60
            'returnExtentOnly': 'false',
61
            'returnGeometry': 'false',
62
            'returnIdsOnly': 'false',
63
            'returnM': 'false',
64
            'returnZ': 'false',
65
            'spatialRel': 'esriSpatialRelIntersects',
66
            'units': 'esriSRUnit_Foot',
89
            'outFields': '*',
67 90
        }
68

  
69
        url = '%s/query' % self.base_url
91
        if lat and lon:
92
            try:
93
                lon, lat = float(lon), float(lat)
94
            except (ValueError,):
95
                raise APIError('<lon> and <lat> must be floats', http_status=400)
96
            params['geometry'] = '{},{}'.format(lon, lat)
97
            params['geometryType'] = 'esriGeometryPoint'
98
        elif latmin and lonmin and latmax and lonmax:
99
            try:
100
                lonmin, latmin = float(lonmin), float(latmin)
101
                lonmax, latmax = float(lonmax), float(latmax)
102
            except (ValueError,):
103
                raise APIError('<lonmin> <latmin> <lonmax> and <latmax> must be floats',
104
                               http_status=400)
105
            params['geometry'] = '{},{},{},{}'.format(lonmin, latmin, lonmax, latmax)
106
            params['geometryType'] = 'esriGeometryEnvelope'
107
        if q is not None:
108
            params['text'] = q
109
        # consider all remaining parameters as ArcGIS ones
110
        params.update(kwargs)
111
        if 'where' not in params and 'text' not in params:
112
            params['where'] = '1=1'
113
        if 'distance' in params and 'units' not in params:
114
            params['units'] = 'esriSRUnit_Meter'
70 115

  
71 116
        response = self.requests.get(url, params=params)
72
        data = response.json()
73
        features = data['features']
74
        if not features:
75
            raise APIError('No features found.')
76 117

  
77
        data = [
78
            {'id': feature['attributes'].get('NUMERO'), 'text': feature['attributes'].get('NOM')} for feature in features]
118
        # errors
119
        if response.status_code // 100 != 2:
120
            raise ArcGISError('ArcGIS returned status code %s' % response.status_code)
121
        try:
122
            infos = response.json()
123
        except (ValueError,):
124
            raise ArcGISError('ArcGIS returned invalid JSON content: %r' % response.content)
125
        if 'error' in infos:
126
            err_desc = infos['error'].get('message') or 'unknown ArcGIS error'
127
            raise ArcGISError(err_desc, data=infos)
128

  
129
        features = infos.pop('features', [])
130
        id_fieldname = infos.get('objectIdFieldName') or 'OBJECTID'
131
        text_fieldname = infos.get('displayFieldName')
132
        if infos.get('fieldAliases'):
133
            aliases = {v: k for k, v in infos['fieldAliases'].items()}
134
        else:
135
            aliases = {}
136

  
137
        # data is the features list, with 'id' and 'text' entries
138
        data = []
79 139

  
80
        if len(data) == 1:
81
            return {'data': data[0]}
140
        def get_feature_attribute(feature, attribute):
141
            if attribute in feature['attributes']:
142
                return feature['attributes'][attribute]
143
            return feature['attributes'].get(aliases.get(attribute))
144

  
145
        if template:
146
            template = Template(template)
147
        if id_template:
148
            id_template = Template(id_template)
149
        for n, feature in enumerate(features):
150
            if 'attributes' in feature:
151
                feature['id'] = '%s' % get_feature_attribute(feature, id_fieldname)
152
                feature['text'] = '%s' % get_feature_attribute(feature, text_fieldname)
153
            else:
154
                feature['id'] = feature['text'] = '%d' % (n+1)
155
            if template:
156
                feature['text'] = template.render(Context(feature))
157
            if id_template:
158
                feature['id'] = id_template.render(Context(feature))
159
            if not full and 'geometry' in feature:
160
                del feature['geometry']
161
            data.append(feature)
162

  
163
        if full:
164
            return {'data': data, 'metadata': infos}
82 165
        return {'data': data}
166

  
167
    @endpoint(name='district',
168
              description=_('Districts in Nancy Town (deprecated)'),
169
              parameters={
170
                  'lat': {'description': _('Latitude')},
171
                  'lon': {'description': _('Longitude')},
172
              })
173
    def district(self, request, lon=None, lat=None):
174
        if 'NANCY_Grc' in self.base_url:
175
            # Nancy URL used to contains folder, service and layer, remove them
176
            self.base_url = 'https://geoservices.grand-nancy.org/arcgis/rest/'
177
        features = self.mapservice_query(request, folder='public', service='NANCY_Grc', layer='0',
178
                                         template='{{ attributes.NOM }}',
179
                                         id_template='{{ attributes.NUMERO }}',
180
                                         lon=lon, lat=lat)['data']
181
        if not features:
182
            raise APIError('No features found.')
183
        for feature in features:
184
            del feature['attributes']
185
        feature['id'] = int(feature['id'])
186
        if len(features) == 1:
187
            return {'data': features[0]}
188
        return {'data': features}
passerelle/contrib/arcgis/templates/arcgis/arcgis_detail.html
1
{% extends "passerelle/manage/service_view.html" %}
2
{% load i18n passerelle %}
3

  
4
{% block endpoints %}
5
<ul>
6
    <li>
7
        {%trans "Request : "%}
8
        {% url "generic-endpoint" connector="arcgis" slug=object.slug endpoint="district" as get_district %}
9
        <a href="{{get_district}}?lat=48.695243&lon=6.178921">{{get_district}}?lat=48.695243&lon=6.178921</a>
10
    </li>
11
    <li>
12
    {%trans "Response : "%}
13
    <samp>
14
        {
15
            "data": {
16
                    "id": 7,
17
                    "name": "SAINT NICOLAS / CHARLES III / VIEILLE VILLE / TROIS MAISONS/ LEOPOLD"
18
                    },
19
            "err": 0
20
        }
21
    </samp>
22
    </li>
23
</ul>
24
{% endblock %}
tests/test_arcgis.py
1
import os
2
import json
3

  
1
# -*- coding: utf-8 -*-
4 2
import pytest
5 3
import mock
4
import utils
6 5

  
7

  
8
from django.core.urlresolvers import reverse
9 6
from django.contrib.contenttypes.models import ContentType
10 7

  
8
from passerelle.contrib.arcgis.models import ArcGIS
11 9
from passerelle.base.models import ApiUser, AccessRight
12
from passerelle.contrib.arcgis.models import Arcgis
13

  
14

  
15
TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'data', 'nancy_arcgis')
16

  
17

  
18
def get_file_content(filename):
19
    with open(os.path.join(TEST_BASE_DIR, filename), 'rb') as fd:
20
        return fd.read()
21 10

  
22

  
23
class MockedRequestsResponse(mock.Mock):
24

  
25
    def json(self):
26
        return json.loads(self.content)
27

  
28

  
29
@pytest.fixture
30
def setup(db):
31
    api = ApiUser.objects.create(username='all',
32
                                 keytype='', key='')
33
    arcgis = Arcgis.objects.create(base_url='https://whatevever.org/layer/0', slug='test')
34
    obj_type = ContentType.objects.get_for_model(arcgis)
35
    AccessRight.objects.create(codename='can_access', apiuser=api,
36
                               resource_type=obj_type, resource_pk=arcgis.pk)
37
    return arcgis
11
# from http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/fold/serv/MapServer/1
12
STATES = '''{
13
   "fieldAliases" : {
14
      "OBJECTID" : "OBJECTID",
15
      "STATE_NAME" : "STATE_NAME",
16
      "STATE_ABBR" : "STATE_ABBR"
17
   },
18
   "features" : [
19
      {
20
         "attributes" : {
21
            "STATE_NAME" : "Texas",
22
            "STATE_ABBR" : "TX",
23
            "OBJECTID" : 40
24
         },
25
         "geometry" : {
26
            "rings" : [
27
               [
28
                  [-105.998886788462, 31.3939400524361],
29
                  [-106.21328556164, 31.4782464373727]
30
               ]
31
            ]
32
         }
33
      },
34
      {
35
         "geometry" : {
36
            "rings" : [
37
               [
38
                  [-111.475425113078, 44.7021622250113],
39
                  [-111.480804007084, 44.6914159859524]
40
               ]
41
            ]
42
         },
43
         "attributes" : {
44
            "STATE_NAME" : "Montana",
45
            "STATE_ABBR" : "MT",
46
            "OBJECTID" : 2
47
         }
48
      }
49
   ],
50
   "spatialReference" : {
51
      "wkid" : 4326
52
   },
53
   "fields" : [
54
      {
55
         "alias" : "OBJECTID",
56
         "type" : "esriFieldTypeOID",
57
         "name" : "OBJECTID"
58
      },
59
      {
60
         "type" : "esriFieldTypeString",
61
         "alias" : "STATE_NAME",
62
         "length" : 25,
63
         "name" : "STATE_NAME"
64
      },
65
      {
66
         "length" : 2,
67
         "alias" : "STATE_ABBR",
68
         "type" : "esriFieldTypeString",
69
         "name" : "STATE_ABBR"
70
      }
71
   ],
72
   "geometryType" : "esriGeometryPolygon",
73
   "displayFieldName" : "STATE_NAME"
74
}'''
38 75

  
39 76

  
40 77
@pytest.fixture
41
def url():
42
    return reverse('generic-endpoint', kwargs={
43
        'connector': 'arcgis', 'slug': 'test', 'endpoint': 'district'})
44

  
45

  
46
def test_get_district_parameters_error(app, setup, url):
47
    resp = app.get(url, params={'lon': 'lon', 'lat': 'lat'}, status=400)
48
    assert resp.json['err_desc'] == '<lon> and <lat> must be floats'
49

  
50

  
51
@mock.patch('passerelle.utils.Request.get')
52
def test_get_district(mocked_get, app, setup, url):
53
    mocked_get.return_value = MockedRequestsResponse(
54
        content=get_file_content('sigresponse.json'),
55
        status_code=200)
56

  
57
    resp = app.get(url, params={'lon': 6.172122, 'lat': 48.673836}, status=200)
58
    data = resp.json['data']
59
    assert data['id'] == 4
60
    assert data['text'] == 'HAUSSONVILLE / BLANDAN / MON DESERT / SAURUPT'
61

  
62

  
63
@mock.patch('passerelle.utils.Request.get')
64
def test_get_all_district(mocked_get, app, setup, url):
65
    mocked_get.return_value = MockedRequestsResponse(
66
        content=get_file_content('all_districts.json'),
67
        status_code=200)
68

  
69
    resp = app.get(url, status=200)
70
    data = resp.json['data']
71
    assert len(data) == 7
72

  
73
@mock.patch('passerelle.utils.Request.get')
74
def test_no_district(mocked_get, app, setup, url):
75
    mocked_get.return_value = MockedRequestsResponse(
76
        content='{"features": []}',
77
        status_code=200)
78

  
79
    resp = app.get(url, status=200)
80
    assert resp.json['err'] == 1
81
    assert resp.json['err_class'] == 'passerelle.utils.jsonresponse.APIError'
82
    assert resp.json['err_desc'] == 'No features found.'
78
def arcgis(db):
79
    return ArcGIS.objects.create(slug='test',
80
                                 base_url='https://arcgis.example.net/')
81

  
82

  
83
def test_arcgis_mapservice_query(app, arcgis):
84
    endpoint = utils.generic_endpoint_url('arcgis', 'mapservice-query', slug=arcgis.slug)
85
    assert endpoint == '/arcgis/test/mapservice-query'
86
    params = {
87
        'folder': 'fold',
88
        'service': 'serv',
89
        'layer': '1'
90
    }
91

  
92
    with mock.patch('passerelle.utils.Request.get') as requests_get:
93
        requests_get.return_value = utils.FakedResponse(content=STATES,
94
                                                        status_code=200)
95

  
96
        resp = app.get(endpoint, params=params, status=403)
97
        assert requests_get.call_count == 0
98
        assert resp.json['err'] == 1
99
        assert resp.json['err_class'] == 'django.core.exceptions.PermissionDenied'
100

  
101
        # open access
102
        api = ApiUser.objects.create(username='all', keytype='', key='')
103
        obj_type = ContentType.objects.get_for_model(arcgis)
104
        AccessRight.objects.create(codename='can_access', apiuser=api, resource_type=obj_type,
105
                                   resource_pk=arcgis.pk)
106
        resp = app.get(endpoint, params=params, status=200)
107
        assert requests_get.call_count == 1
108
        assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query'
109
        args = requests_get.call_args[1]['params']
110
        assert args['f'] == 'json'
111
        assert args['outFields'] == '*'
112
        assert args['where'] == '1=1'
113
        assert 'data' in resp.json
114
        assert resp.json['err'] == 0
115
        assert len(resp.json['data']) == 2
116
        assert resp.json['data'][0]['id'] == '40'
117
        assert resp.json['data'][0]['text'] == 'Texas'
118
        assert 'geometry' not in resp.json['data'][0]
119
        assert 'metadata' not in resp.json
120

  
121
        params['full'] = 'on'
122
        resp = app.get(endpoint, params=params, status=200)
123
        assert requests_get.call_count == 2
124
        assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query'
125
        args = requests_get.call_args[1]['params']
126
        assert args['f'] == 'json'
127
        assert args['outFields'] == '*'
128
        assert args['where'] == '1=1'
129
        assert 'data' in resp.json
130
        assert resp.json['err'] == 0
131
        assert len(resp.json['data']) == 2
132
        assert resp.json['data'][0]['id'] == '40'
133
        assert resp.json['data'][0]['text'] == 'Texas'
134
        assert resp.json['data'][0]['geometry']
135
        assert resp.json['metadata']
136

  
137
        params['q'] = 'Texas'
138
        resp = app.get(endpoint, params=params, status=200)
139
        assert requests_get.call_count == 3
140
        assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query'
141
        args = requests_get.call_args[1]['params']
142
        assert args['text'] == 'Texas'
143
        assert 'where' not in args
144

  
145
        params['lat'] = '9.87654'
146
        params['lon'] = '1.12345'
147
        resp = app.get(endpoint, params=params, status=200)
148
        assert requests_get.call_count == 4
149
        assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query'
150
        args = requests_get.call_args[1]['params']
151
        assert args['geometry'] == '1.12345,9.87654'
152
        assert args['geometryType'] == 'esriGeometryPoint'
153

  
154
        del params['lat']  # missing lat, do not search by geometry
155
        resp = app.get(endpoint, params=params, status=200)
156
        assert requests_get.call_count == 5
157
        assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query'
158
        args = requests_get.call_args[1]['params']
159
        assert 'geometry' not in args
160
        assert 'geometryType' not in args
161

  
162
        params.update({'latmin': '1', 'lonmin': '2', 'latmax': '3', 'lonmax': '4'})
163
        resp = app.get(endpoint, params=params, status=200)
164
        assert requests_get.call_count == 6
165
        assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query'
166
        args = requests_get.call_args[1]['params']
167
        assert args['geometry'] == '2.0,1.0,4.0,3.0'
168
        assert args['geometryType'] == 'esriGeometryEnvelope'
169

  
170
        del params['latmin']  # incomplete box, do not search by geometry
171
        resp = app.get(endpoint, params=params, status=200)
172
        assert requests_get.call_count == 7
173
        assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query'
174
        args = requests_get.call_args[1]['params']
175
        assert 'geometry' not in args
176
        assert 'geometryType' not in args
177

  
178
        # others params are directly sent to ArcGIS
179
        params['spatialRel'] = 'esriSpatialRelContains'
180
        params.update({'latmin': '1', 'lonmin': '2', 'latmax': '3', 'lonmax': '4'})
181
        resp = app.get(endpoint, params=params, status=200)
182
        assert requests_get.call_count == 8
183
        assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/fold/serv/MapServer/1/query'
184
        args = requests_get.call_args[1]['params']
185
        assert args['geometry'] == '2.0,1.0,4.0,3.0'
186
        assert args['geometryType'] == 'esriGeometryEnvelope'
187
        assert args['spatialRel'] == 'esriSpatialRelContains'
188

  
189
        # folder
190
        params['folder'] = 'foo/bar'
191
        resp = app.get(endpoint, params=params, status=200)
192
        assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/foo/bar/serv/MapServer/1/query'
193
        del params['folder']
194
        resp = app.get(endpoint, params=params, status=200)
195
        assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/serv/MapServer/1/query'
196

  
197
        # minimal call
198
        resp = app.get(endpoint, params={'service': 'srv'}, status=200)
199
        assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/srv/MapServer/0/query'
200
        args = requests_get.call_args[1]['params']
201
        assert args == {'f': 'json', 'inSR': '4326', 'outSR': '4326',
202
                        'outFields': '*', 'where': '1=1'}
203

  
204
        # distance
205
        resp = app.get(endpoint, params={'service': 'srv', 'distance': '100'}, status=200)
206
        assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/srv/MapServer/0/query'
207
        args = requests_get.call_args[1]['params']
208
        assert args['distance'] == '100'
209
        assert args['units'] == 'esriSRUnit_Meter'  # default unit
210
        resp = app.get(endpoint, params={'service': 'srv', 'distance': '5', 'units':
211
                                         'esriSRUnit_NauticalMile'}, status=200)
212
        assert requests_get.call_args[0][0] == 'https://arcgis.example.net/services/srv/MapServer/0/query'
213
        args = requests_get.call_args[1]['params']
214
        assert args['distance'] == '5'
215
        assert args['units'] == 'esriSRUnit_NauticalMile'
216

  
217
    # call errors
218
    with mock.patch('passerelle.utils.Request.get') as requests_get:
219
        requests_get.return_value = utils.FakedResponse(content=STATES,
220
                                                        status_code=200)
221
        resp = app.get(endpoint, params={}, status=400)
222
        assert requests_get.call_count == 0
223
        assert resp.json['err'] == 1
224
        assert resp.json['err_class'] == 'passerelle.views.WrongParameter'
225
        assert resp.json['err_desc'] == "missing parameters: 'service'."
226

  
227
        resp = app.get(endpoint, params={'service': 'src', 'lat': '0', 'lon': 'y'}, status=400)
228
        assert requests_get.call_count == 0
229
        assert resp.json['err'] == 1
230
        assert resp.json['err_class'] == 'passerelle.utils.jsonresponse.APIError'
231
        assert resp.json['err_desc'] == '<lon> and <lat> must be floats'
232

  
233
        resp = app.get(endpoint, params={'service': 'src', 'latmin': '0', 'lonmin': 'y',
234
                                         'latmax': '0', 'lonmax': '1'}, status=400)
235
        assert requests_get.call_count == 0
236
        assert resp.json['err'] == 1
237
        assert resp.json['err_class'] == 'passerelle.utils.jsonresponse.APIError'
238
        assert resp.json['err_desc'] == '<lonmin> <latmin> <lonmax> and <latmax> must be floats'
tests/test_arcgis_nancy.py
1
import os
2
import json
3

  
4
import pytest
5
import mock
6

  
7

  
8
from django.core.urlresolvers import reverse
9
from django.contrib.contenttypes.models import ContentType
10

  
11
from passerelle.base.models import ApiUser, AccessRight
12
from passerelle.contrib.arcgis.models import ArcGIS
13

  
14

  
15
TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'data', 'nancy_arcgis')
16

  
17

  
18
def get_file_content(filename):
19
    with open(os.path.join(TEST_BASE_DIR, filename), 'rb') as fd:
20
        return fd.read()
21

  
22

  
23
class MockedRequestsResponse(mock.Mock):
24

  
25
    def json(self):
26
        return json.loads(self.content)
27

  
28

  
29
@pytest.fixture
30
def setup(db):
31
    api = ApiUser.objects.create(username='all',
32
                                 keytype='', key='')
33
    arcgis = ArcGIS.objects.create(base_url='https://example.net/layer/0', slug='test')
34
    obj_type = ContentType.objects.get_for_model(arcgis)
35
    AccessRight.objects.create(codename='can_access', apiuser=api,
36
                               resource_type=obj_type, resource_pk=arcgis.pk)
37
    return arcgis
38

  
39

  
40
@pytest.fixture
41
def url():
42
    return reverse('generic-endpoint', kwargs={
43
        'connector': 'arcgis', 'slug': 'test', 'endpoint': 'district'})
44

  
45

  
46
def test_get_district_parameters_error(app, setup, url):
47
    resp = app.get(url, params={'lon': 'lon', 'lat': 'lat'}, status=400)
48
    assert resp.json['err_desc'] == '<lon> and <lat> must be floats'
49

  
50

  
51
@mock.patch('passerelle.utils.Request.get')
52
def test_get_district(mocked_get, app, setup, url):
53
    mocked_get.return_value = MockedRequestsResponse(
54
        content=get_file_content('sigresponse.json'),
55
        status_code=200)
56

  
57
    resp = app.get(url, params={'lon': 6.172122, 'lat': 48.673836}, status=200)
58
    data = resp.json['data']
59
    assert data['id'] == 4
60
    assert data['text'] == 'HAUSSONVILLE / BLANDAN / MON DESERT / SAURUPT'
61

  
62

  
63
@mock.patch('passerelle.utils.Request.get')
64
def test_get_all_district(mocked_get, app, setup, url):
65
    mocked_get.return_value = MockedRequestsResponse(
66
        content=get_file_content('all_districts.json'),
67
        status_code=200)
68

  
69
    resp = app.get(url, status=200)
70
    data = resp.json['data']
71
    assert len(data) == 7
72

  
73
@mock.patch('passerelle.utils.Request.get')
74
def test_no_district(mocked_get, app, setup, url):
75
    mocked_get.return_value = MockedRequestsResponse(
76
        content='{"features": []}',
77
        status_code=200)
78

  
79
    resp = app.get(url, status=200)
80
    assert resp.json['err'] == 1
81
    assert resp.json['err_class'] == 'passerelle.utils.jsonresponse.APIError'
82
    assert resp.json['err_desc'] == 'No features found.'
tests/test_generic_endpoint.py
28 28

  
29 29
from passerelle.base.models import BaseResource, ResourceLog, ProxyLogger
30 30
from passerelle.contrib.mdel.models import MDEL
31
from passerelle.contrib.arcgis.models import Arcgis
31
from passerelle.contrib.arcgis.models import ArcGIS
32 32
from passerelle.contrib.stub_invoices.models import StubInvoicesConnector
33 33
from passerelle.utils.api import endpoint
34 34

  
......
40 40

  
41 41
@pytest.fixture
42 42
def arcgis(db):
43
    return utils.setup_access_rights(Arcgis.objects.create(slug='test', log_level='DEBUG'))
43
    return utils.setup_access_rights(ArcGIS.objects.create(slug='test', log_level='DEBUG'))
44 44

  
45 45

  
46 46
DEMAND_STATUS = {
47
-