Projet

Général

Profil

0001-opendatasoft-add-opendatasoft-connector-40979.patch

Nicolas Roche, 15 avril 2020 15:12

Télécharger (25,6 ko)

Voir les différences:

Subject: [PATCH] opendatasoft: add opendatasoft connector (#40979)

 passerelle/apps/opendatasoft/__init__.py      |   0
 .../opendatasoft/migrations/0001_initial.py   |  55 ++++
 .../apps/opendatasoft/migrations/__init__.py  |   0
 passerelle/apps/opendatasoft/models.py        | 148 ++++++++++
 .../opendatasoft/query_confirm_delete.html    |   1 +
 .../templates/opendatasoft/query_form.html    |   1 +
 passerelle/apps/opendatasoft/urls.py          |  28 ++
 passerelle/apps/opendatasoft/views.py         |  47 +++
 passerelle/settings.py                        |   1 +
 passerelle/static/css/style.css               |   5 +
 tests/test_opendatasoft.py                    | 274 ++++++++++++++++++
 11 files changed, 560 insertions(+)
 create mode 100644 passerelle/apps/opendatasoft/__init__.py
 create mode 100644 passerelle/apps/opendatasoft/migrations/0001_initial.py
 create mode 100644 passerelle/apps/opendatasoft/migrations/__init__.py
 create mode 100644 passerelle/apps/opendatasoft/models.py
 create mode 100644 passerelle/apps/opendatasoft/templates/opendatasoft/query_confirm_delete.html
 create mode 100644 passerelle/apps/opendatasoft/templates/opendatasoft/query_form.html
 create mode 100644 passerelle/apps/opendatasoft/urls.py
 create mode 100644 passerelle/apps/opendatasoft/views.py
 create mode 100644 tests/test_opendatasoft.py
passerelle/apps/opendatasoft/migrations/0001_initial.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2020-04-15 07:53
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6
import django.db.models.deletion
7
import passerelle.utils.templates
8

  
9

  
10
class Migration(migrations.Migration):
11

  
12
    initial = True
13

  
14
    dependencies = [
15
        ('base', '0018_smslog'),
16
    ]
17

  
18
    operations = [
19
        migrations.CreateModel(
20
            name='OpenDataSoft',
21
            fields=[
22
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
23
                ('title', models.CharField(max_length=50, verbose_name='Title')),
24
                ('slug', models.SlugField(unique=True, verbose_name='Identifier')),
25
                ('description', models.TextField(verbose_name='Description')),
26
                ('service_url', models.CharField(default='https://examples.opendatasoft.com', help_text='OpenData Adresse Web Service URL', max_length=256, verbose_name='Service URL')),
27
                ('api_key', models.CharField(blank=True, default='', help_text='API key used as credentials', max_length=128, verbose_name='API key')),
28
                ('users', models.ManyToManyField(blank=True, related_name='_opendatasoft_users_+', related_query_name='+', to='base.ApiUser')),
29
            ],
30
            options={
31
                'verbose_name': 'OpenDataSoft Web Service',
32
            },
33
        ),
34
        migrations.CreateModel(
35
            name='Query',
36
            fields=[
37
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
38
                ('name', models.CharField(max_length=128, verbose_name='Name')),
39
                ('slug', models.SlugField(max_length=128, verbose_name='Slug')),
40
                ('description', models.TextField(blank=True, verbose_name='Description')),
41
                ('dataset', models.CharField(default='world-heritage-unesco-list', help_text='dataset to query', max_length=128, verbose_name='Dataset')),
42
                ('text_template', models.TextField(blank=True, default='{{name_fr}} en/au {{country_fr}}', help_text="Use Django's template syntax. Attributes can be accessed through {{ attributes.name }}", validators=[passerelle.utils.templates.validate_template], verbose_name='Text template')),
43
                ('resource', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='queries', to='opendatasoft.OpenDataSoft', verbose_name='Resource')),
44
            ],
45
            options={
46
                'verbose_name': 'Query',
47
                'ordering': ['name'],
48
                'abstract': False,
49
            },
50
        ),
51
        migrations.AlterUniqueTogether(
52
            name='query',
53
            unique_together=set([('resource', 'name'), ('resource', 'slug')]),
54
        ),
55
    ]
passerelle/apps/opendatasoft/models.py
1
# passerelle - uniform access to multiple data sources and services
2
# Copyright (C) 2020  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from django.db import models
18
from django.shortcuts import get_object_or_404
19
from django.template import Context, Template
20
from django.core.urlresolvers import reverse
21
from django.utils.encoding import force_text
22
from django.utils.six.moves.urllib import parse as urlparse
23
from django.utils.translation import ugettext_lazy as _
24

  
25
from passerelle.utils.templates import validate_template
26
from passerelle.base.models import BaseResource, BaseQuery
27
from passerelle.utils.api import endpoint
28

  
29

  
30
class OpenDataSoft(BaseResource):
31
    service_url = models.CharField(
32
        _('Service URL'),
33
        max_length=256, blank=False,
34
        help_text=_('OpenData Adresse Web Service URL'),
35
        default='https://examples.opendatasoft.com',
36
    )
37
    api_key = models.CharField(
38
        _('API key'),
39
        max_length=128, blank=True,
40
        help_text=_('API key used as credentials'),
41
        default='',
42
    )
43

  
44
    category = _('Data Sources')
45
    documentation_url = 'https://doc-publik.entrouvert.com/admin-fonctionnel/parametrage-avance/connecteur-opendadasoft/'
46

  
47
    class Meta:
48
        verbose_name = _('OpenDataSoft Web Service')
49

  
50
    @endpoint(
51
        perm='can_access',
52
        description=_('Search'),
53
        parameters={
54
            'dataset': {'description': _('Dataset'), 'example_value': 'world-heritage-unesco-list'},
55
            'text_template': {'description': _('Text template'), 'example_value': '{{name_fr}} en/au {{country_fr}}'},
56
            'id': {'description': _('Record identifier')},
57
            'q': {'description': _('Full text query'), 'example_value': "plage"},
58
            'rows': {'description': _('Maximum items')},
59
        })
60
    def search(self, request, dataset=None, text_template='', id=None, q=None, rows=None, **kwargs):
61
        scheme, netloc, path, params, query, fragment = urlparse.urlparse(self.service_url)
62
        path = urlparse.urljoin(path, 'api/records/1.0/search/')
63
        url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
64

  
65
        if id is not None:
66
            query = 'recordid:%s' % id
67
        else:
68
            query = q
69
        params = {
70
            'dataset': dataset,
71
            'q': query,
72
        }
73
        if self.api_key:
74
            params.update({'apikey': self.api_key})
75
        if rows:
76
            params.update({'rows': rows})
77

  
78
        result_response = self.requests.get(url, params=params)
79

  
80
        result = []
81
        for record in result_response.json().get('records'):
82
            data = {}
83
            data['id'] = record.get('recordid')
84

  
85
            context = {}
86
            for key, value in record.get('fields').items():
87
                context[key] = force_text(value)
88
            template = Template(text_template)
89
            data['text'] = template.render(Context(context)).strip()
90

  
91
            result.append(data)
92
        return {'data': result}
93

  
94
    @endpoint(name='q',
95
              description=_('Query'),
96
              pattern=r'^(?P<query_slug>[\w:_-]+)/$',
97
              perm='can_access',
98
              show=False)
99
    def q(self, request, query_slug, **kwargs):
100
        query = get_object_or_404(Query, resource=self, slug=query_slug)
101
        return query.q(request, **kwargs)
102

  
103
    def create_query_url(self):
104
        return reverse('opendatasoft-query-new', kwargs={'slug': self.slug})
105

  
106

  
107
class Query(BaseQuery):
108
    resource = models.ForeignKey(
109
        to=OpenDataSoft,
110
        related_name='queries',
111
        verbose_name=_('Resource'))
112
    dataset = models.CharField(
113
        _('Dataset'),
114
        max_length=128, blank=False,
115
        help_text=_('dataset to query'),
116
        default='world-heritage-unesco-list',
117
    )
118
    text_template = models.TextField(
119
        verbose_name=_('Text template'),
120
        help_text=_(
121
            "Use Django's template syntax. Attributes can be accessed through {{ attributes.name }}"
122
        ),
123
        default='{{name_fr}} en/au {{country_fr}}',
124
        validators=[validate_template],
125
        blank=True
126
    )
127

  
128
    delete_view = 'opendatasoft-query-delete'
129
    edit_view = 'opendatasoft-query-edit'
130

  
131
    def q(self, request, **kwargs):
132
        return self.resource.search(
133
            request, dataset=self.dataset, text_template=self.text_template, **kwargs)
134

  
135
    def as_endpoint(self):
136
        endpoint = super(Query, self).as_endpoint(path=self.resource.q.endpoint_info.name)
137

  
138
        search_endpoint = self.resource.search.endpoint_info
139
        endpoint.func = search_endpoint.func
140
        endpoint.show_undocumented_params = False
141

  
142
        # Copy generic params descriptions from original endpoint
143
        # if they are not overloaded by the query
144
        for param in search_endpoint.parameters:
145
            if param in ('dataset', 'text_template') and getattr(self, param):
146
                continue
147
            endpoint.parameters[param] = search_endpoint.parameters[param]
148
        return endpoint
passerelle/apps/opendatasoft/templates/opendatasoft/query_confirm_delete.html
1
{% extends "passerelle/manage/resource_child_confirm_delete.html" %}
passerelle/apps/opendatasoft/templates/opendatasoft/query_form.html
1
{% extends "passerelle/manage/resource_child_form.html" %}
passerelle/apps/opendatasoft/urls.py
1
# passerelle - uniform access to multiple data sources and services
2
# Copyright (C) 2020 Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from django.conf.urls import url
18

  
19
from . import views
20

  
21
management_urlpatterns = [
22
    url(r'^(?P<slug>[\w,-]+)/query/new/$',
23
        views.QueryNew.as_view(), name='opendatasoft-query-new'),
24
    url(r'^(?P<slug>[\w,-]+)/query/(?P<pk>\d+)/$',
25
        views.QueryEdit.as_view(), name='opendatasoft-query-edit'),
26
    url(r'^(?P<slug>[\w,-]+)/query/(?P<pk>\d+)/delete/$',
27
        views.QueryDelete.as_view(), name='opendatasoft-query-delete'),
28
]
passerelle/apps/opendatasoft/views.py
1
# passerelle - uniform access to multiple data sources and services
2
# Copyright (C) 2020 Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from django import forms
18
from django.views.generic import UpdateView, CreateView, DeleteView
19

  
20
from passerelle.base.mixins import ResourceChildViewMixin
21

  
22
from . import models
23

  
24

  
25
class QueryForm(forms.ModelForm):
26
    class Meta:
27
        model = models.Query
28
        fields = '__all__'
29
        exclude = ['resource']
30

  
31

  
32
class QueryNew(ResourceChildViewMixin, CreateView):
33
    model = models.Query
34
    form_class = QueryForm
35

  
36
    def form_valid(self, form):
37
        form.instance.resource = self.resource
38
        return super(QueryNew, self).form_valid(form)
39

  
40

  
41
class QueryEdit(ResourceChildViewMixin, UpdateView):
42
    model = models.Query
43
    form_class = QueryForm
44

  
45

  
46
class QueryDelete(ResourceChildViewMixin, DeleteView):
47
    model = models.Query
passerelle/settings.py
141 141
    'passerelle.apps.gdc',
142 142
    'passerelle.apps.gesbac',
143 143
    'passerelle.apps.jsondatastore',
144 144
    'passerelle.apps.sp_fr',
145 145
    'passerelle.apps.mdel',
146 146
    'passerelle.apps.mdel_ddpacs',
147 147
    'passerelle.apps.mobyt',
148 148
    'passerelle.apps.okina',
149
    'passerelle.apps.opendatasoft',
149 150
    'passerelle.apps.opengis',
150 151
    'passerelle.apps.orange',
151 152
    'passerelle.apps.ovh',
152 153
    'passerelle.apps.oxyd',
153 154
    'passerelle.apps.pastell',
154 155
    'passerelle.apps.phonecalls',
155 156
    'passerelle.apps.solis',
156 157
    'passerelle.apps.vivaticket',
passerelle/static/css/style.css
176 176
li.connector.dpark a::before {
177 177
	content: "\f1b9";  /* car */
178 178
}
179 179

  
180 180
li.connector.cryptor a::before {
181 181
	content: "\f023";  /* lock */
182 182
}
183 183

  
184
li.connector.opendatasoft a::before {
185
	content: "\f1ad"; /* building */
186
}
187

  
188

  
184 189
li.connector.status-down span.connector-name::after {
185 190
	font-family: FontAwesome;
186 191
	content: "\f00d"; /* times */
187 192
	color: #CD2026;
188 193
	padding-left: 1ex;
189 194
	margin-left: 1ex;
190 195
	border-left: 1px solid #003388;
191 196
}
tests/test_opendatasoft.py
1
# -*- coding: utf-8 -*-
2
# passerelle - uniform access to multiple data sources and services
3
# Copyright (C) 2020  Entr'ouvert
4
#
5
# This program is free software: you can redistribute it and/or modify it
6
# under the terms of the GNU Affero General Public License as published
7
# by the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU Affero General Public License for more details.
14
#
15
# You should have received a copy of the GNU Affero General Public License
16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17

  
18
import mock
19
import json
20
import pytest
21

  
22
import utils
23

  
24
from passerelle.apps.opendatasoft.models import OpenDataSoft, Query
25

  
26
from test_manager import login, admin_user
27

  
28

  
29
FAKED_CONTENT_Q_SEARCH = json.dumps({
30
    "nhits": 76,
31
    "parameters": {
32
        "dataset": "referentiel-adresse-test",
33
        "format": "json",
34
        "q": "rue de l'aubepine",
35
        "rows": 3,
36
        "timezone": "UTC"
37
    },
38
    "records": [
39
        {
40
            "datasetid": "referentiel-adresse-test",
41
            "fields": {
42
                "adresse_complete": "33 RUE DE L'AUBEPINE STRASBOURG",
43
                "date_exprt": "2019-10-23",
44
                "geo_point": [
45
                    48.6060963542,
46
                    7.76978279836
47
                ],
48
                "nom_commun": "Strasbourg",
49
                "nom_rue": "RUE DE L'AUBEPINE",
50
                "num_com": 482,
51
                "numero": "33",
52
                "source": u"Ville et Eurométropole de Strasbourg"
53
            },
54
            "geometry": {
55
                "coordinates": [
56
                    7.76978279836,
57
                    48.6060963542
58
                ],
59
                "type": "Point"
60
            },
61
            "record_timestamp": "2019-12-02T14:15:08.376000+00:00",
62
            "recordid": "e00cf6161e52a4c8fe510b2b74d4952036cb3473"
63
        },
64
        {
65
            "datasetid": "referentiel-adresse-test",
66
            "fields": {
67
                "adresse_complete": "19 RUE DE L'AUBEPINE LIPSHEIM",
68
                "date_exprt": "2019-10-23",
69
                "geo_point": [
70
                    48.4920620548,
71
                    7.66177412454
72
                ],
73
                "nom_commun": "Lipsheim",
74
                "nom_rue": "RUE DE L'AUBEPINE",
75
                "num_com": 268,
76
                "numero": "19",
77
                "source": u"Ville et Eurométropole de Strasbourg"
78
            },
79
            "geometry": {
80
                "coordinates": [
81
                    7.66177412454,
82
                    48.4920620548
83
                ],
84
                "type": "Point"
85
            },
86
            "record_timestamp": "2019-12-02T14:15:08.376000+00:00",
87
            "recordid": "7cafcd5c692773e8b863587b2d38d6be82e023d8"
88
        },
89
        {
90
            "datasetid": "referentiel-adresse-test",
91
            "fields": {
92
                "adresse_complete": "29 RUE DE L'AUBEPINE STRASBOURG",
93
                "date_exprt": "2019-10-23",
94
                "geo_point": [
95
                    48.6056497224,
96
                    7.76988497729
97
                ],
98
                "nom_commun": "Strasbourg",
99
                "nom_rue": "RUE DE L'AUBEPINE",
100
                "num_com": 482,
101
                "numero": "29",
102
                "source": u"Ville et Eurométropole de Strasbourg"
103
            },
104
            "geometry": {
105
                "coordinates": [
106
                    7.76988497729,
107
                    48.6056497224
108
                ],
109
                "type": "Point"
110
            },
111
            "record_timestamp": "2019-12-02T14:15:08.376000+00:00",
112
            "recordid": "0984a5e1745701f71c91af73ce764e1f7132e0ff"
113
        }
114
    ]
115
})
116

  
117
FAKED_CONTENT_ID_SEARCH = json.dumps({
118
    "nhits": 1,
119
    "parameters": {
120
        "dataset": "referentiel-adresse-test",
121
        "format": "json",
122
        "q": "recordid:7cafcd5c692773e8b863587b2d38d6be82e023d8",
123
        "rows": 1,
124
        "timezone": "UTC"
125
    },
126
    "records": [
127
        {
128
            "datasetid": "referentiel-adresse-test",
129
            "fields": {
130
                "adresse_complete": "19 RUE DE L'AUBEPINE LIPSHEIM",
131
                "date_exprt": "2019-10-23",
132
                "geo_point": [
133
                    48.4920620548,
134
                    7.66177412454
135
                ],
136
                "nom_commun": "Lipsheim",
137
                "nom_rue": "RUE DE L'AUBEPINE",
138
                "num_com": 268,
139
                "numero": "19",
140
                u"source": "Ville et Eurométropole de Strasbourg"
141
            },
142
            "geometry": {
143
                "coordinates": [
144
                    7.66177412454,
145
                    48.4920620548
146
                ],
147
                "type": "Point"
148
            },
149
            "record_timestamp": "2019-12-02T14:15:08.376000+00:00",
150
            "recordid": "7cafcd5c692773e8b863587b2d38d6be82e023d8"
151
        }
152
    ]
153
})
154

  
155

  
156
@pytest.fixture
157
def connector(db):
158
    return utils.setup_access_rights(OpenDataSoft.objects.create(
159
        slug='my_connector',
160
        api_key='my_secret',
161
    ))
162

  
163

  
164
@pytest.fixture
165
def query(connector):
166
    return Query.objects.create(
167
        resource=connector,
168
        name='Référenciel adresses de test',
169
        slug='my_query',
170
        description='Rechercher une adresse',
171
        dataset='referentiel-adresse-test',
172
        text_template='{{numero}} {{nom_rue|safe}} {{nom_commun}}',
173
    )
174

  
175

  
176
def test_views(db, admin_user, app, connector):
177
    app = login(app)
178
    resp = app.get('/opendatasoft/my_connector/', status=200)
179
    resp = resp.click('New Query')
180
    resp.form['name'] = 'my query'
181
    resp.form['slug'] = 'my-query'
182
    resp = resp.form.submit()
183
    resp = resp.follow()
184
    assert [x.text for x in resp.html.find_all('span', {'class': 'description'})]
185

  
186

  
187
@mock.patch('passerelle.utils.Request.get')
188
def test_search_using_q(mocked_get, app, connector):
189
    endpoint = utils.generic_endpoint_url('opendatasoft', 'search', slug=connector.slug)
190
    assert endpoint == '/opendatasoft/my_connector/search'
191
    params = {
192
        'dataset': 'referentiel-adresse-test',
193
        'text_template': '{{numero}} {{nom_rue|safe}} {{nom_commun}}',
194
        'q': "rue de l'aubepine",
195
        'rows': 3,
196
    }
197
    mocked_get.return_value = utils.FakedResponse(content=FAKED_CONTENT_Q_SEARCH, status_code=200)
198
    resp = app.get(endpoint, params=params, status=200)
199
    assert not resp.json['err']
200
    assert len(resp.json['data']) == 3
201
    # order is keept
202
    assert resp.json['data'][0] == {
203
        'id': 'e00cf6161e52a4c8fe510b2b74d4952036cb3473',
204
        'text': "33 RUE DE L'AUBEPINE Strasbourg"
205
    }
206
    assert resp.json['data'][1] == {
207
        'id': '7cafcd5c692773e8b863587b2d38d6be82e023d8',
208
        'text': "19 RUE DE L'AUBEPINE Lipsheim"
209
    }
210
    assert resp.json['data'][2] == {
211
        'id': '0984a5e1745701f71c91af73ce764e1f7132e0ff',
212
        'text': "29 RUE DE L'AUBEPINE Strasbourg"
213
    }
214

  
215

  
216
@mock.patch('passerelle.utils.Request.get')
217
def test_search_using_id(mocked_get, app, connector):
218
    endpoint = utils.generic_endpoint_url('opendatasoft', 'search', slug=connector.slug)
219
    assert endpoint == '/opendatasoft/my_connector/search'
220
    params = {
221
        'dataset': 'referentiel-adresse-test',
222
        'text_template': '{{numero}} {{nom_rue|safe}} {{nom_commun}}',
223
        'id': '7cafcd5c692773e8b863587b2d38d6be82e023d8',
224
    }
225
    mocked_get.return_value = utils.FakedResponse(content=FAKED_CONTENT_ID_SEARCH, status_code=200)
226
    resp = app.get(endpoint, params=params, status=200)
227
    assert resp.json == {
228
        'err': 0,
229
        'data': [{
230
            'id': '7cafcd5c692773e8b863587b2d38d6be82e023d8',
231
            'text': "19 RUE DE L'AUBEPINE Lipsheim"
232
        }]}
233

  
234

  
235
@mock.patch('passerelle.utils.Request.get')
236
def test_query_q_using_q(mocked_get, app, query):
237
    endpoint = '/opendatasoft/my_connector/q/my_query/'
238
    params = {
239
        'q': "rue de l'aubepine",
240
        'rows': 3,
241
    }
242
    mocked_get.return_value = utils.FakedResponse(content=FAKED_CONTENT_Q_SEARCH, status_code=200)
243
    resp = app.get(endpoint, params=params, status=200)
244
    assert not resp.json['err']
245
    assert len(resp.json['data']) == 3
246
    # order is keept
247
    assert resp.json['data'][0] == {
248
        'id': 'e00cf6161e52a4c8fe510b2b74d4952036cb3473',
249
        'text': "33 RUE DE L'AUBEPINE Strasbourg"
250
    }
251
    assert resp.json['data'][1] == {
252
        'id': '7cafcd5c692773e8b863587b2d38d6be82e023d8',
253
        'text': "19 RUE DE L'AUBEPINE Lipsheim"
254
    }
255
    assert resp.json['data'][2] == {
256
        'id': '0984a5e1745701f71c91af73ce764e1f7132e0ff',
257
        'text': "29 RUE DE L'AUBEPINE Strasbourg"
258
    }
259

  
260

  
261
@mock.patch('passerelle.utils.Request.get')
262
def test_query_q_using_id(mocked_get, app, query):
263
    endpoint = '/opendatasoft/my_connector/q/my_query/'
264
    params = {
265
        'id': '7cafcd5c692773e8b863587b2d38d6be82e023d8',
266
    }
267
    mocked_get.return_value = utils.FakedResponse(content=FAKED_CONTENT_ID_SEARCH, status_code=200)
268
    resp = app.get(endpoint, params=params, status=200)
269
    assert resp.json == {
270
        'err': 0,
271
        'data': [{
272
            'id': '7cafcd5c692773e8b863587b2d38d6be82e023d8',
273
            'text': "19 RUE DE L'AUBEPINE Lipsheim"
274
        }]}
0
-