Projet

Général

Profil

0001-add-connector-for-API-Particulier-14838.patch

Benjamin Dauvergne, 01 février 2017 14:08

Télécharger (14,1 ko)

Voir les différences:

Subject: [PATCH] add connector for API-Particulier (#14838)

 passerelle/apps/api_particulier/__init__.py |   0
 passerelle/apps/api_particulier/models.py   | 147 +++++++++++++++++++
 passerelle/settings.py                      |   1 +
 tests/test_api_particulier.py               | 210 ++++++++++++++++++++++++++++
 tests/utils.py                              |  23 +++
 5 files changed, 381 insertions(+)
 create mode 100644 passerelle/apps/api_particulier/__init__.py
 create mode 100644 passerelle/apps/api_particulier/models.py
 create mode 100644 tests/test_api_particulier.py
passerelle/apps/api_particulier/models.py
1
# passerelle.apps.api_particulier
2
# Copyright (C) 2017  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
'''Gateway to API-Particulier web-service from SGMAP:
18
   https://particulier.api.gouv.fr/
19
'''
20

  
21
from urlparse import urljoin
22
from collections import OrderedDict
23

  
24
from django.db import models
25
from django.utils.translation import ugettext_lazy as _
26

  
27
from passerelle.base.models import BaseResource
28
from passerelle.utils.api import endpoint
29
from passerelle.utils.jsonresponse import APIError
30

  
31

  
32
class APIParticulier(BaseResource):
33
    PLATFORMS = [
34
        {
35
            'name': 'test',
36
            'label': _('Test'),
37
            'url': 'https://particulier-test.api.gouv.fr/api/',
38
            'api_key': 'test-token',
39
        },
40
        {
41
            'name': 'prod',
42
            'label': _('Production'),
43
            'url': 'https://particulier.api.gouv.fr/api/'
44
        },
45
        {
46
            'name': 'dev',
47
            'label': _('Development'),
48
            'url': 'https://particulier-dev.api.gouv.fr/api/'
49
        },
50
        {
51
            'name': 'mock',
52
            'label': _('Mock'),
53
            'url': 'https://particulier-mock.api.gouv.fr/api/'
54
        },
55
    ]
56
    PLATFORMS = OrderedDict([(platform['name'], platform) for platform in PLATFORMS])
57

  
58
    _platform = models.CharField(
59
        verbose_name=_('platform'),
60
        max_length=8,
61
        choices=[(key, platform['label']) for key, platform in PLATFORMS.iteritems()])
62

  
63
    _api_key = models.CharField(
64
        max_length=64,
65
        default='',
66
        blank=True,
67
        verbose_name=_('API key'))
68

  
69
    @property
70
    def platform(self):
71
        return self.PLATFORMS[self._platform]
72

  
73
    @property
74
    def url(self):
75
        return self.platform['url']
76

  
77
    @property
78
    def api_key(self):
79
        return self.platform.get('api_key', self._api_key)
80

  
81
    def get(self, path, **kwargs):
82
        user = kwargs.pop('user', None)
83
        url = urljoin(self.url, path)
84
        headers = {'X-API-KEY': self.api_key}
85
        if user:
86
            headers['X-User'] = user
87
        response = self.requests.get(
88
            url,
89
            headers=headers,
90
            **kwargs)
91

  
92
        if response.status_code != 200:
93
            raise APIError(
94
                u'API-particulier platform "%s" returned non-200 code: %s' %
95
                (self._platform, response.status_code),
96
                data={
97
                    'platform': self._platform,
98
                    'code': response.status_code,
99
                    'content': repr(response.content[:1000]),
100
                })
101
        try:
102
            return response.json()
103
        except ValueError as e:
104
            content = repr(response.content[:1000])
105
            raise APIError(
106
                u'API-particulier platform "%s" returned non-JSON content: %s' %
107
                (self._platform, content),
108
                data={
109
                    'exception': unicode(e),
110
                    'platform': self._platform,
111
                    'content': content,
112
                })
113

  
114
    @endpoint(serializer_type='json-api', perm='can_access')
115
    def impots_svair(self, request, numero_fiscal, reference_avis, user=None):
116
        return self.get('impots/svair', params={
117
            'numeroFiscal': numero_fiscal,
118
            'referenceAvis': reference_avis,
119
        }, user=user)
120

  
121
    @endpoint(serializer_type='json-api', perm='can_access')
122
    def impots_adresse(self, request, numero_fiscal, reference_avis, user=None):
123
        return self.get('impots/adress', params={
124
            'numeroFiscal': numero_fiscal,
125
            'referenceAvis': reference_avis,
126
        }, user=user)
127

  
128
    @endpoint(serializer_type='json-api', perm='can_access')
129
    def caf_qf(self, request, code_postal, numero_allocataire, user=None):
130
        return self.get('caf/qf', params={
131
            'codePostal': code_postal,
132
            'numeroAllocataire': numero_allocataire,
133
        }, user=user)
134

  
135
    @endpoint(serializer_type='json-api', perm='can_access')
136
    def caf_adresse(self, request, code_postal, numero_allocataire, user=None):
137
        return self.get('caf/adresse', params={
138
            'codePostal': code_postal,
139
            'numeroAllocataire': numero_allocataire,
140
        }, user=user)
141

  
142
    @endpoint(serializer_type='json-api', perm='can_access')
143
    def caf_famille(self, request, code_postal, numero_allocataire, user=None):
144
        return self.get('caf/famille', params={
145
            'codePostal': code_postal,
146
            'numeroAllocataire': numero_allocataire,
147
        }, user=user)
passerelle/settings.py
113 113
    'csvdatasource',
114 114
    'orange',
115 115
    'family',
116
    'api_particulier',
116 117
    # backoffice templates and static
117 118
    'gadjo',
118 119
)
tests/test_api_particulier.py
1
# -*- coding: utf-8 -*-
2

  
3
# tests/test_api_particulier.py
4
# Copyright (C) 2017  Entr'ouvert
5
#
6
# This program is free software: you can redistribute it and/or modify it
7
# under the terms of the GNU Affero General Public License as published
8
# by the Free Software Foundation, either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU Affero General Public License for more details.
15
#
16
# You should have received a copy of the GNU Affero General Public License
17
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18

  
19
import pytest
20
from httmock import urlmatch, HTTMock, response
21

  
22
from api_particulier.models import APIParticulier
23

  
24
from utils import make_ressource, endpoint_get
25

  
26
SVAIR_RESPONSE = {
27
    "declarant1": {
28
        "nom": "Martin",
29
        "nomNaissance": "Martin",
30
        "prenoms": "Pierre",
31
        "dateNaissance": "22/03/1985"
32
    },
33
    "declarant2": {
34
        "nom": "Martin",
35
        "nomNaissance": "Honore",
36
        "prenoms": "Marie",
37
        "dateNaissance": "03/04/1986"
38
    },
39
    "foyerFiscal": {
40
        "annee": 2015,
41
        "adresse": "12 rue Balzac 75008 Paris"
42
    },
43
    "dateRecouvrement": "10/10/2015",
44
    "dateEtablissement": "08/07/2015",
45
    "nombreParts": 2,
46
    "situationFamille": "Marié(e)s",
47
    "nombrePersonnesCharge": 2,
48
    "revenuBrutGlobal": 29880,
49
    "revenuImposable": 29880,
50
    "impotRevenuNetAvantCorrections": 2165,
51
    "montantImpot": 2165,
52
    "revenuFiscalReference": 29880,
53
    "anneeImpots": "2015",
54
    "anneeRevenus": "2014"
55
}
56

  
57
IMPOTS_ADRESSE = {
58
    "adresses": [
59
        {
60
            "adresse": {
61
                "citycode": "75108",
62
                "street": "Rue Balzac",
63
                "name": "12 Rue Balzac",
64
                "housenumber": "12",
65
                "city": "Paris",
66
                "type": "housenumber",
67
                "context": "75, Île-de-France",
68
                "score": 0.9401454545454544,
69
                "label": "12 Rue Balzac 75008 Paris",
70
                "postcode": "75008"
71
            },
72
            "geometry": {
73
                "type": "Point",
74
                "coordinates": [
75
                    2.300816,
76
                    48.873951
77
                ]
78
            }
79
        }
80
    ],
81
    "declarant1": {
82
        "nom": "Martin",
83
        "nomNaissance": "Martin",
84
        "prenoms": "Pierre",
85
        "dateNaissance": "22/03/1985"
86
    },
87
    "declarant2": {
88
        "nom": "Martin",
89
        "nomNaissance": "Honore",
90
        "prenoms": "Marie",
91
        "dateNaissance": "03/04/1986"
92
    },
93
    "foyerFiscal": {
94
        "annee": 2015,
95
        "adresse": "12 rue Balzac 75008 Paris"
96
    }
97
}
98

  
99

  
100
@urlmatch(netloc='^particulier.*\.api\.gouv\.fr$',
101
          path='^/api/impots/svair$')
102
def api_particulier_impots_svair(url, request):
103
    return response(200, SVAIR_RESPONSE, request=request)
104

  
105

  
106
@urlmatch(netloc='^particulier.*\.api\.gouv\.fr$',
107
          path='^/api/impots/adresse$')
108
def api_particulier_impots_adresse(url, request):
109
    return response(200, IMPOTS_ADRESSE, request=request)
110

  
111

  
112
@urlmatch(netloc='^particulier.*\.api\.gouv\.fr$')
113
def api_particulier_error_500(url, request):
114
    return response(500, 'something bad happened', request=request)
115

  
116
@urlmatch(netloc='^particulier.*\.api\.gouv\.fr$')
117
def api_particulier_error_not_json(url, request):
118
    return response(200, 'something bad happened', request=request)
119

  
120
@pytest.yield_fixture
121
def mock_api_particulier():
122
    with HTTMock(api_particulier_impots_svair, api_particulier_impots_adresse):
123
        yield None
124

  
125

  
126
@pytest.fixture
127
def ressource(db):
128
    return make_ressource(
129
        APIParticulier,
130
        slug='test',
131
        title='API Particulier Prod',
132
        description='API Particulier Prod',
133
        _platform='test')
134

  
135

  
136
def test_error(app, ressource, mock_api_particulier):
137
    with HTTMock(api_particulier_error_500):
138
        def do(endpoint, params):
139
            resp = endpoint_get(
140
                app,
141
                ressource,
142
                endpoint,
143
                params=params)
144
            assert resp.status_code == 200
145
            assert resp.json['err'] == 1
146
            assert resp.json['data']['code'] == 500
147
        vector = [
148
            (['impots_svair', 'impots_adresse'], {
149
                'numero_fiscal': 12,
150
                'reference_avis': 15,
151
                'user': 'john.doe',
152
            }),
153
            (['caf_qf', 'caf_adresse', 'caf_famille'], {
154
                'code_postal': 12,
155
                'numero_allocataire': 15
156
            }),
157
        ]
158
        for endpoints, params in vector:
159
            for endpoint in endpoints:
160
                do(endpoint, params)
161
    with HTTMock(api_particulier_error_not_json):
162
        def do(endpoint, params):
163
            resp = endpoint_get(
164
                app,
165
                ressource,
166
                endpoint,
167
                params=params)
168
            assert resp.status_code == 200
169
            assert resp.json['err'] == 1
170
            assert resp.json['data']['exception'] == 'No JSON object could be decoded'
171
        vector = [
172
            (['impots_svair', 'impots_adresse'], {
173
                'numero_fiscal': 12,
174
                'reference_avis': 15,
175
                'user': 'john.doe',
176
            }),
177
            (['caf_qf', 'caf_adresse', 'caf_famille'], {
178
                'code_postal': 12,
179
                'numero_allocataire': 15
180
            }),
181
        ]
182
        for endpoints, params in vector:
183
            for endpoint in endpoints:
184
                do(endpoint, params)
185

  
186

  
187
def test_impots_svair(app, ressource, mock_api_particulier):
188
    resp = endpoint_get(
189
        app,
190
        ressource,
191
        'impots_svair',
192
        params={
193
            'numero_fiscal': 12,
194
            'reference_avis': 15
195
        })
196
    assert resp.json['data']['montantImpot'] == 2165
197

  
198

  
199
def test_impots_adresse(app, ressource, mock_api_particulier):
200
    resp = endpoint_get(
201
        app,
202
        ressource,
203
        'impots_adresse',
204
        params={
205
            'numero_fiscal': 12,
206
            'reference_avis': 15
207
        })
208
    assert resp.json['data']['adresses'][0]['adresse']['citycode'] == '75108'
209

  
210
# FIXME: CAF web services are currently broken, add test eventually when we can test them
tests/utils.py
39 39
    def mocked(url, request):
40 40
        return response
41 41
    return httmock.HTTMock(mocked)
42

  
43

  
44
def make_ressource(model_class, **kwargs):
45
    api, created = ApiUser.objects.get_or_create(
46
        username='all',
47
        keytype='',
48
        key='')
49
    ressource = model_class.objects.create(**kwargs)
50
    obj_type = ContentType.objects.get_for_model(model_class)
51
    AccessRight.objects.get_or_create(
52
        codename='can_access',
53
        apiuser=api,
54
        resource_type=obj_type,
55
        resource_pk=ressource.pk)
56
    return ressource
57

  
58

  
59
def endpoint_get(app, ressource, endpoint, **kwargs):
60
    url = generic_endpoint_url(
61
        connector=ressource.__class__.get_connector_slug(),
62
        endpoint=endpoint,
63
        slug=ressource.slug)
64
    return app.get(url, **kwargs)
42
-