add MDPH13 connector (#30692)

This commit is contained in:
Benjamin Dauvergne 2019-02-15 10:57:26 +01:00
parent 6884d84f8c
commit ad953198e9
7 changed files with 865 additions and 0 deletions

View File

View File

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.10 on 2019-02-15 09:57
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('base', '0010_loggingparameters_trace_emails'),
]
operations = [
migrations.CreateModel(
name='Link',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name_id', models.CharField(max_length=256, verbose_name='NameID')),
('file_number', models.CharField(max_length=64, verbose_name='MDPH beneficiary file number')),
('secret', models.CharField(max_length=64, verbose_name='MDPH beneficiary secret')),
('dob', models.DateField(verbose_name='MDPH beneficiary date of birth')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Creation date')),
],
options={
'ordering': ['file_number'],
},
),
migrations.CreateModel(
name='MDPH13Resource',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=50, verbose_name='Title')),
('description', models.TextField(verbose_name='Description')),
('slug', models.SlugField(unique=True, verbose_name='Identifier')),
('basic_auth_username', models.CharField(blank=True, max_length=128, verbose_name='Basic authentication username')),
('basic_auth_password', models.CharField(blank=True, max_length=128, verbose_name='Basic authentication password')),
('client_certificate', models.FileField(blank=True, null=True, upload_to=b'', verbose_name='TLS client certificate')),
('trusted_certificate_authorities', models.FileField(blank=True, null=True, upload_to=b'', verbose_name='TLS trusted CAs')),
('verify_cert', models.BooleanField(default=True, verbose_name='TLS verify certificates')),
('http_proxy', models.CharField(blank=True, max_length=128, verbose_name='HTTP and HTTPS proxy')),
('webservice_base_url', models.URLField(verbose_name='Webservice Base URL')),
('users', models.ManyToManyField(blank=True, to='base.ApiUser')),
],
options={
'verbose_name': 'MDPH CD13',
},
),
migrations.AddField(
model_name='link',
name='resource',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mdph13.MDPH13Resource'),
),
migrations.AlterUniqueTogether(
name='link',
unique_together=set([('resource', 'name_id', 'file_number')]),
),
]

View File

@ -0,0 +1,340 @@
# -*- coding: utf-8 -*-
# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
import urlparse
import datetime
import re
import requests
from django.db import models, transaction
from django.utils import six
from django.utils.translation import ugettext_lazy as _
from passerelle.utils.jsonresponse import APIError, to_json
from passerelle.utils.api import endpoint
from passerelle.base.models import BaseResource, HTTPResource
class MDPH13Resource(BaseResource, HTTPResource):
category = _('Business Process Connectors')
webservice_base_url = models.URLField(_('Webservice Base URL'))
EMAIL_RE = re.compile(r'^[^@\s]+@[^@\s]+\.[^@\s]+$')
class Meta:
verbose_name = _('MDPH CD13')
def situation_dossier_url(self, file_number):
return urlparse.urljoin(self.webservice_base_url, 'situation/dossier/%s' % file_number)
def url_get(self, *args, **kwargs):
try:
response = None
response = self.requests.get(*args, **kwargs)
response.raise_for_status()
except requests.RequestException as e:
data = {'exception': six.text_type(e)}
if response is not None:
try:
if response.json():
data['body'] = response.json()
except ValueError:
body = response.content[:1000]
if len(response.content) > 1000:
body += b'<SNIPPED>'
data['body'] = repr(body)
data['status_code'] = response.status_code
raise APIError('HTTP request failed', data=data)
try:
content = response.json()
except ValueError as e:
data = {'exception': six.text_type(e)}
data['body'] = '%r<SNIPPED>' % response.content[:1000]
raise APIError('HTTP request did not respond with JSON', data=data)
return content
def call_situation_dossier(self, file_number, secret, dob, email=None):
url = self.situation_dossier_url(file_number)
email = email or 'appel-sans-utilisateur@cd13.fr'
error_mapping = {
'dossier-inconnu': 'dossier-inconnu',
'secret-invalide': 'secret-invalide',
'dateNaissance-erronee': 'date-de-naissance-erronee',
}
try:
content = self.url_get(
url,
headers={
'X-CD13-Secret': base64.b64encode(secret.encode('utf-8')).decode('ascii'),
'X-CD13-Email': email,
'X-CD13-DateNaissBenef': dob.isoformat(),
})
except APIError as e:
if e.data and e.data.get('status_code') == 404 and isinstance(e.data.get('body'), dict):
content = e.data['body']
if content.get('err') != 0:
error_code = content.get('err_code')
err_desc = error_mapping.get(error_code, 'err != 0')
raise APIError(err_desc, data=e.data)
raise
if content.get('err') != 0:
error_code = content.get('err_code')
err_desc = error_mapping.get(error_code, 'err != 0')
raise APIError(err_desc, data=content)
data = content.get('data')
if not isinstance(data, dict):
raise APIError('data-must-be-a-dict', data=content)
if str(data.get('numero')) != str(file_number):
raise APIError('numero-must-match-numero-dossier', date=content)
# Reorganize entourage
beneficiaire = data.get('beneficiaire', {})
entourage = beneficiaire.get('entourage')
if entourage is not None:
if not isinstance(entourage, list):
raise APIError('entourage-must-be-a-list', data=content)
if not all(isinstance(person, dict) for person in entourage):
raise APIError('demandes-content-must-be-dicts', data=content)
new_entourage = {}
for person in entourage:
if not isinstance(person, dict):
raise APIError('entourage-content-must-be-dicts', data=content)
if person.get('role') in [u'Père', u'Mère']:
new_entourage.setdefault('parents', []).append(person)
else:
new_entourage.setdefault('aidants', []).append(person)
beneficiaire['entourage'] = new_entourage
# Reorganize demandes
demandes = data.get('demandes')
if demandes:
if not isinstance(demandes, list):
raise APIError('demandes-must-be-a-list', data=content)
if not all(isinstance(demande, dict) for demande in demandes):
raise APIError('demandes-content-must-be-dicts', data=content)
new_demandes = {}
typologies = {
u'demande en cours': 'en_cours',
u'traitée et expédiée': 'historique',
u'traitée non expédiée': 'historique',
}
if not all(isinstance(demande.get('typologie'), six.text_type) for demande in demandes):
raise APIError('typologie-must-be-a-string', data=content)
if not all(demande['typologie'].lower() in typologies for demande in demandes):
unknowns = set([demande['typologie'].lower() for demande in demandes]) - set(typologies.keys())
raise APIError('typologie-is-unknown',
data={
'unknowns': list(unknowns),
'choices': typologies.keys()
})
for demande in demandes:
new_demandes.setdefault(
typologies[demande['typologie'].lower()],
[]
).append(demande)
data['demandes'] = new_demandes
return data
def check_status(self):
try:
link = Link.objects.latest('created')
except Link.DoesNotExist:
return
# no email passed, it's a background check
link.get_file()
@endpoint(name='link',
methods=['post'],
description=_('Create link with an extranet account'),
perm='can_access',
parameters={
'NameID': {
'description': _('Publik NameID'),
'example_value': 'xyz24d934',
},
'numero_dossier': {
'description': _('MDPH13 beneficiary file number'),
'example_value': '1234',
},
'secret': {
'description': _('MDPH13 beneficiary secret'),
'example_value': 'secret',
},
'date_de_naissance': {
'description': _('MDPH13 beneficiary date of birth'),
'example_value': '1992-03-05',
},
'email': {
'description': _('Publik known email'),
'example_value': 'john.doe@example.com',
},
})
def link(self, request, NameID, numero_dossier, secret, date_de_naissance, email):
file_number = numero_dossier.strip()
try:
int(file_number)
except ValueError:
raise APIError('numero_dossier must be a number', http_status=400)
try:
dob = datetime.datetime.strptime(date_de_naissance.strip(), '%Y-%m-%d').date()
except ValueError:
raise APIError('date_de_naissance must be a date YYYY-MM-DD', http_status=400)
email = email.strip()
if not self.EMAIL_RE.match(email):
raise APIError('email is not valid', http_status=400)
with transaction.atomic():
link, created = Link.objects.get_or_create(
resource=self,
name_id=NameID,
file_number=file_number,
defaults={
'secret': secret,
'dob': dob
})
updated = False
if not created:
if link.secret != secret or link.dob != dob:
link.secret = secret
link.dob = dob
updated = True
# email is necessary for audit purpose
link.get_file(email=email)
if updated:
link.save()
return {'link_id': link.pk, 'created': created, 'updated': updated}
@endpoint(name='unlink',
methods=['post', 'delete'],
description=_('Delete link with an extranet account'),
perm='can_access',
parameters={
'NameID': {
'description': _('Publik NameID'),
'example_value': 'xyz24d934',
},
'link_id': {
'description': _('Identifier of the link'),
'example_value': '1',
},
})
def unlink(self, request, NameID, link_id):
qs = Link.objects.filter(resource=self, name_id=NameID)
if link_id == 'all':
pass # unlink of all links
else:
try:
link_id = int(link_id.strip())
except ValueError:
raise APIError('link_id-must-be-a-number')
qs = qs.filter(pk=link_id)
count = qs.count()
qs.delete()
return {'deleted': count}
@endpoint(name='dossiers',
description=_('Get datas for all links, or for a specified one'),
perm='can_access',
parameters={
'NameID': {
'description': _('Publik NameID'),
'example_value': 'xyz24d934',
},
'email': {
'description': _('Publik known email'),
'example_value': 'john.doe@example.com',
},
'link_id': {
'description': _('Link identifier'),
'example_value': '1',
}
})
def dossiers(self, request, NameID, email, link_id=None):
email = email.strip()
if not self.EMAIL_RE.match(email):
raise APIError('email is not valid', http_status=400)
qs = Link.objects.filter(
resource=self,
name_id=NameID)
if link_id:
try:
link_id = int(link_id)
except ValueError:
raise APIError('invalid-link-id', http_status=400)
qs = qs.filter(id=link_id)
data = []
for link in qs:
file_data = {
'id': str(link.id),
'err': 0,
}
try:
mdph_file = link.get_file(email=email)
except Exception as e:
if link_id:
raise
file_data.update(to_json().err_to_response(e))
else:
file_data.update({
'numero_dossier': link.file_number,
'date_de_naissance': link.dob.isoformat(),
'dossier': mdph_file,
})
data.append(file_data)
if link_id:
return {'data': data[0] if data else None}
return {'data': data}
class Link(models.Model):
resource = models.ForeignKey(
MDPH13Resource,
on_delete=models.CASCADE)
name_id = models.CharField(
verbose_name=_('NameID'),
max_length=256)
file_number = models.CharField(
max_length=64,
verbose_name=_('MDPH beneficiary file number'))
secret = models.CharField(
verbose_name=_('MDPH beneficiary secret'),
max_length=64)
dob = models.DateField(
verbose_name=_('MDPH beneficiary date of birth'))
created = models.DateTimeField(
verbose_name=_('Creation date'),
auto_now_add=True)
def get_file(self, email=None):
# email is necessary for audit purpose
return self.resource.call_situation_dossier(
file_number=self.file_number,
secret=self.secret,
dob=self.dob,
email=email)
class Meta:
unique_together = (
'resource', 'name_id', 'file_number',
)
ordering = ['file_number']

View File

@ -27,6 +27,7 @@ INSTALLED_APPS += (
'passerelle.contrib.iws',
'passerelle.contrib.maarch',
'passerelle.contrib.mdel',
'passerelle.contrib.mdph13',
'passerelle.contrib.meyzieu_newsletters',
'passerelle.contrib.nancypoll',
'passerelle.contrib.planitech',

462
tests/test_mdph13.py Normal file
View File

@ -0,0 +1,462 @@
# -*- coding: utf-8 -*-
# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2018 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import json
import base64
import datetime
import httmock
import pytest
import utils
from passerelle.utils.jsonresponse import APIError
from passerelle.contrib.mdph13.models import MDPH13Resource, Link
NAME_ID = 'xyz'
FILE_NUMBER = '1234'
DOB = datetime.date(1993, 5, 4)
DOB_ISOFORMAT = '1993-05-04'
EMAIL = 'john.doe@example.com'
SECRET = 'secret'
VALID_RESPONSE = json.dumps({
'err': 0,
"data": {
"numero": FILE_NUMBER,
"beneficiaire": {
"nom": "MARTINI",
"prenom": "ALFONSO",
"tel_mobile": "06 01 02 03 04",
"tel_fixe": "04.01.02.03.04",
"date_de_naissance": "1951-03-23",
"email": "martini.a@free.fr",
"entourage": [
{
"role": "Père",
"nom": "DUPONT Henri",
"tel_mobile": "0123232323",
"tel_fixe": "0202020202",
"email": "henri.dupont@xyz.com"
},
{
"role": "Mère",
"nom": "DUPONT Marie",
"tel_mobile": "0123232323",
"tel_fixe": "0202020202",
"email": "marie.dupont@xyz.com"
},
{
"role": "Aidant",
"nom": "ROBERT Fanny",
"tel_mobile": "0123232323",
"tel_fixe": "0202020202",
"email": "frobert@xyz.com"
}
],
"adresse": {
"adresse_2": "Bliblibli",
"adresse_3": "Bliblibli",
"adresse_4": "CHEMIN DE LA CARRAIRE",
"adresse_5": "Bliblibli",
"code_postal": "13500",
"ville": "MARTIGUES"
},
"incapacite": {
"taux": "Taux >=80%",
"date_fin_effet": "2019-06-30"
}
},
"demandes": [
{
"numero": "1544740",
"date_demande": "2015-11-26",
"type_demande": "Renouvellement",
"prestation": "Carte d'invalidité (de priorité) pour personne handicapée",
"statut": "Instruction administrative terminée en attente de passage en évaluation",
"typologie": "Demande En Cours",
"date_decision": "2015-01-16"
},
{
"numero": "1210524",
"date_demande": "2014-06-13",
"type_demande": "Renouvellement",
"prestation": "Carte d'invalidité (de priorité) pour personne handicapée",
"statut": "Décision prononcée et expédition réalisée (traitement terminé)",
"typologie": "Traitée non expédiée",
"date_decision": "2014-07-10",
"date_debut_effet": "2014-08-01",
"date_fin_effet": "2016-05-01"
},
{
"numero": "1231345",
"date_demande": "2014-07-22",
"type_demande": "Recours Gracieux",
"prestation": "Carte d'invalidité (de priorité) pour personne handicapée",
"statut": "Décision prononcée et expédition réalisée (traitement terminé)",
"typologie": "Traitée et expédiée",
"date_decision": "2014-09-17",
"date_debut_effet": "2014-08-01",
"date_fin_effet": "2016-05-01"
},
{
"numero": "666660",
"date_demande": "2012-08-13",
"type_demande": "Recours Gracieux",
"prestation": "Carte d'invalidité (de priorité) pour personne handicapée",
"statut": "Décision prononcée et expédition réalisée (traitement terminé)",
"typologie": "Traitée et expédiée",
"date_decision": "2012-09-26",
"date_debut_effet": "2012-07-19",
"date_fin_effet": "2014-08-01"
},
{
"numero": "605280",
"date_demande": "2012-04-05",
"type_demande": "1ère demande",
"prestation": "Carte d'invalidité (de priorité) pour personne handicapée",
"statut": "Décision prononcée et expédition réalisée (traitement terminé)",
"typologie": "Traitée et expédiée",
"date_decision": "2012-07-19",
"date_debut_effet": "2012-07-19",
"date_fin_effet": "2014-05-01"
},
{
"numero": "1544741",
"date_demande": "2015-11-26",
"type_demande": "Renouvellement",
"prestation": "Carte d'invalidité (de priorité) pour personne handicapée",
"statut": "Décision prononcée et expédition réalisée (traitement terminé)",
"typologie": "Traitée et expédiée",
"date_decision": "2015-12-22",
"date_debut_effet": "2016-05-01",
"date_fin_effet": "2026-05-01"
},
{
"numero": "1210526",
"date_demande": "2014-06-13",
"type_demande": "Renouvellement",
"prestation": "Carte européenne de Stationnement",
"statut": "Décision prononcée et expédition réalisée (traitement terminé)",
"typologie": "Traitée et expédiée",
"date_decision": "2014-07-04",
"date_debut_effet": "2014-05-01",
"date_fin_effet": "2015-05-01"
},
{
"numero": "605281",
"date_demande": "2012-04-05",
"type_demande": "1ère demande",
"prestation": "Carte européenne de Stationnement",
"statut": "Décision prononcée et expédition réalisée (traitement terminé)",
"typologie": "Traitée et expédiée",
"date_decision": "2012-07-04",
"date_debut_effet": "2012-05-01",
"date_fin_effet": "2014-05-01"
}
]
}
})
DOSSIER_INCONNU = {
'status_code': 404,
'content': json.dumps({
'err': 1,
'err_code': 'dossier-inconnu',
}),
}
SECRET_INVALIDE = {
'status_code': 404,
'content': json.dumps({
'err': 1,
'err_code': 'secret-invalide',
}),
}
@pytest.fixture
def mdph13(db):
return utils.make_resource(
MDPH13Resource,
title='Test 1',
slug='test1',
description='Connecteur de test',
webservice_base_url='http://cd13.fr/')
@pytest.fixture
def mock_http():
class MockHttp(object):
def __init__(self):
self.requests = []
self.responses = []
def add_response(self, response):
self.responses.append(response)
@property
def last_request(self):
return self.requests[-1]
def request_handler(self, url, request):
idx = len(self.requests)
self.requests.append(request)
return self.responses[idx]
mock_http = MockHttp()
with httmock.HTTMock(httmock.urlmatch()(mock_http.request_handler)):
yield mock_http
def test_situation_dossier_url(mdph13):
assert mdph13.situation_dossier_url(1234) == 'http://cd13.fr/situation/dossier/1234'
def test_call_situation_dossier(mdph13, mock_http):
mock_http.add_response(VALID_RESPONSE)
mdph13.call_situation_dossier(1234, SECRET, DOB)
request = mock_http.last_request
headers = request.headers
url = request.url
assert url == 'http://cd13.fr/situation/dossier/1234'
assert headers['X-CD13-Secret'] == base64.b64encode(SECRET.encode('utf-8')).decode('ascii')
assert headers['X-CD13-DateNaissBenef'] == '1993-05-04'
assert headers['X-CD13-Email'] == 'appel-sans-utilisateur@cd13.fr'
def test_call_situation_dossier_with_email(mdph13, mock_http):
mock_http.add_response(VALID_RESPONSE)
mdph13.call_situation_dossier(1234, SECRET, DOB, email=EMAIL)
request = mock_http.last_request
headers = request.headers
url = request.url
assert url == 'http://cd13.fr/situation/dossier/1234'
assert headers['X-CD13-Secret'] == base64.b64encode(SECRET.encode('utf-8')).decode('ascii')
assert headers['X-CD13-DateNaissBenef'] == DOB_ISOFORMAT
assert headers['X-CD13-Email'] == EMAIL
def test_check_status_no_link(mdph13):
assert Link.objects.count() == 0
try:
mdph13.check_status()
except Exception as e:
pytest.fail('check_status() should not raise')
def test_check_status_with_link_nok(mdph13, mock_http):
mock_http.add_response({'status_code': 500})
Link.objects.create(
resource=mdph13,
name_id=NAME_ID,
file_number=FILE_NUMBER,
secret=SECRET,
dob=DOB)
assert Link.objects.count() == 1
with pytest.raises(Exception):
mdph13.check_status()
def test_check_status_with_link_ok(mdph13, mock_http):
mock_http.add_response(VALID_RESPONSE)
Link.objects.create(
resource=mdph13,
name_id=NAME_ID,
file_number=FILE_NUMBER,
secret=SECRET,
dob=DOB)
assert Link.objects.count() == 1
try:
mdph13.check_status()
except Exception:
pytest.fail('check_status() should not raise')
def test_link_bad_file_number(mdph13):
with pytest.raises(APIError) as e:
mdph13.link(request=None, NameID=NAME_ID, numero_dossier='x', secret=None,
date_de_naissance=None, email=None)
assert str(e.value) == 'numero_dossier must be a number'
def test_link_bad_date_de_naissance(mdph13):
with pytest.raises(APIError) as e:
mdph13.link(request=None, NameID=NAME_ID, numero_dossier=FILE_NUMBER, secret=None,
date_de_naissance='34-45-6', email=None)
assert str(e.value) == 'date_de_naissance must be a date YYYY-MM-DD'
def test_link_bad_email(mdph13):
with pytest.raises(APIError) as e:
mdph13.link(request=None, NameID=NAME_ID, numero_dossier=FILE_NUMBER, secret=None,
date_de_naissance=DOB_ISOFORMAT, email='xxx@@vvv')
assert str(e.value) == 'email is not valid'
def test_link_nok_dossier_inconnu(mdph13, mock_http):
mock_http.add_response(DOSSIER_INCONNU)
with pytest.raises(APIError) as e:
mdph13.link(request=None, NameID=NAME_ID, numero_dossier=FILE_NUMBER, secret=SECRET,
date_de_naissance=DOB_ISOFORMAT, email=EMAIL)
assert str(e.value) == 'dossier-inconnu'
def test_link_nok_secret_invalide(mdph13, mock_http):
mock_http.add_response(SECRET_INVALIDE)
with pytest.raises(APIError) as e:
mdph13.link(request=None, NameID=NAME_ID, numero_dossier=FILE_NUMBER, secret=SECRET,
date_de_naissance=DOB_ISOFORMAT, email=EMAIL)
assert str(e.value) == 'secret-invalide'
def test_link_numero_dont_match(mdph13, mock_http):
mock_http.add_response(json.dumps({'err': 0, 'data': {'numero': '456'}}))
with pytest.raises(APIError) as e:
mdph13.link(request=None, NameID=NAME_ID, numero_dossier=FILE_NUMBER, secret=SECRET,
date_de_naissance=DOB_ISOFORMAT, email=EMAIL)
assert str(e.value) == 'numero-must-match-numero-dossier'
def test_link_ok(mdph13, mock_http):
# check first time link
mock_http.add_response(VALID_RESPONSE)
assert not Link.objects.count()
response = mdph13.link(request=None, NameID=NAME_ID,
numero_dossier=FILE_NUMBER, secret=SECRET,
date_de_naissance=DOB_ISOFORMAT, email=EMAIL)
link = Link.objects.get()
assert response == {
'link_id': link.pk,
'created': True,
'updated': False
}
# check relinking with update
mock_http.add_response(VALID_RESPONSE)
response = mdph13.link(request=None, NameID=NAME_ID,
numero_dossier=FILE_NUMBER, secret=SECRET+'a',
date_de_naissance=DOB_ISOFORMAT, email=EMAIL)
assert response == {
'link_id': link.pk,
'created': False,
'updated': True,
}
def test_unlink_nok_bad_link_id(mdph13):
with pytest.raises(APIError) as e:
mdph13.unlink(None, None, 'e')
assert str(e.value) == 'link_id-must-be-a-number'
def test_unlink_ok(mdph13):
link = Link.objects.create(
resource=mdph13,
name_id=NAME_ID,
file_number=FILE_NUMBER,
secret=SECRET,
dob=DOB)
result = mdph13.unlink(None, NAME_ID, str(link.pk))
assert result['deleted'] == 1
result = mdph13.unlink(None, NAME_ID, str(link.pk))
assert result['deleted'] == 0
def test_unlink_all_ok(mdph13):
Link.objects.create(
resource=mdph13,
name_id=NAME_ID,
file_number=FILE_NUMBER,
secret=SECRET,
dob=DOB)
Link.objects.create(
resource=mdph13,
name_id=NAME_ID,
file_number='12345',
secret=SECRET,
dob=DOB)
result = mdph13.unlink(None, NAME_ID, 'all')
assert result['deleted'] == 2
result = mdph13.unlink(None, NAME_ID, 'all')
assert result['deleted'] == 0
def test_dossier_ok(mdph13, mock_http):
link = Link.objects.create(
resource=mdph13,
name_id=NAME_ID,
file_number=FILE_NUMBER,
secret=SECRET,
dob=DOB)
mock_http.add_response(VALID_RESPONSE)
response = mdph13.dossiers(None, NAME_ID, EMAIL)
assert response['data']
assert response['data'][0]['id'] == str(link.pk)
assert response['data'][0]['numero_dossier'] == FILE_NUMBER
assert response['data'][0]['date_de_naissance'] == DOB.isoformat()
assert response['data'][0]['dossier']['numero'] == FILE_NUMBER
assert len(response['data'][0]['dossier']['beneficiaire']['entourage']) == 2
assert len(response['data'][0]['dossier']['beneficiaire']['entourage']['parents']) == 2
assert len(response['data'][0]['dossier']['beneficiaire']['entourage']['aidants']) == 1
assert len(response['data'][0]['dossier']['demandes']) == 2
assert len(response['data'][0]['dossier']['demandes']['en_cours']) == 1
assert len(response['data'][0]['dossier']['demandes']['historique']) == 7
def test_dossier_with_link_id_ok(mdph13, mock_http):
link = Link.objects.create(
resource=mdph13,
name_id=NAME_ID,
file_number=FILE_NUMBER,
secret=SECRET,
dob=DOB)
mock_http.add_response(VALID_RESPONSE)
response = mdph13.dossiers(None, NAME_ID, EMAIL, link_id=str(link.pk))
assert response['data']
assert response['data']['id'] == str(link.pk)
assert response['data']['numero_dossier'] == FILE_NUMBER
assert response['data']['date_de_naissance'] == DOB.isoformat()
assert response['data']['dossier']['numero'] == FILE_NUMBER
assert len(response['data']['dossier']['beneficiaire']['entourage']) == 2
assert len(response['data']['dossier']['beneficiaire']['entourage']['parents']) == 2
assert len(response['data']['dossier']['beneficiaire']['entourage']['aidants']) == 1
assert len(response['data']['dossier']['demandes']) == 2
assert len(response['data']['dossier']['demandes']['en_cours']) == 1
assert len(response['data']['dossier']['demandes']['historique']) == 7
def test_dossier_partial_failure(mdph13, mock_http):
link1 = Link.objects.create(
resource=mdph13,
name_id=NAME_ID,
file_number=FILE_NUMBER,
secret=SECRET,
dob=DOB)
link2 = Link.objects.create(
resource=mdph13,
name_id=NAME_ID,
file_number=FILE_NUMBER + '2',
secret=SECRET,
dob=DOB)
mock_http.add_response(VALID_RESPONSE)
mock_http.add_response({'status_code': 500, 'content': ''})
response = mdph13.dossiers(None, NAME_ID, EMAIL)
assert response['data']
assert response['data'][0]['id'] == str(link1.pk)
assert response['data'][0]['err'] == 0
assert response['data'][1]['id'] == str(link2.pk)
assert response['data'][1]['err'] == 1

View File

@ -29,6 +29,7 @@ deps =
mohawk
pytest-freezegun
pytest-httpbin
pytest-localserver
commands =
django18: py.test {posargs: {env:FAST:} --junitxml=test_{envname}_results.xml --cov-report xml --cov-report html --cov=passerelle/ --cov-config .coveragerc tests/}
django18: ./pylint.sh passerelle/