From c238a9f2b06668959c7c3c1691618025630a758e Mon Sep 17 00:00:00 2001 From: Nicolas ROCHE Date: Tue, 12 Jul 2022 18:56:47 +0200 Subject: [PATCH] toulouse_maelis: parsifal: add webservices to manage RL (#67326) --- passerelle/contrib/toulouse_maelis/models.py | 102 +++++ passerelle/contrib/toulouse_maelis/schemas.py | 285 +++++++++++++- .../data/toulouse_maelis/Q_create_family.xml | 28 ++ .../toulouse_maelis/Q_update_coordinate.xml | 25 ++ .../data/toulouse_maelis/Q_update_family.xml | 29 ++ .../data/toulouse_maelis/R_create_family.xml | 11 + .../toulouse_maelis/R_create_family_error.xml | 11 + tests/data/toulouse_maelis/R_is_rl_exists.xml | 8 + .../toulouse_maelis/R_update_coordinate.xml | 6 + .../data/toulouse_maelis/R_update_family.xml | 32 ++ .../toulouse_maelis/R_update_family_error.xml | 16 + .../R_update_family_soap_error.xml | 16 + tests/test_toulouse_maelis.py | 351 ++++++++++++++++++ 13 files changed, 919 insertions(+), 1 deletion(-) create mode 100644 tests/data/toulouse_maelis/Q_create_family.xml create mode 100644 tests/data/toulouse_maelis/Q_update_coordinate.xml create mode 100644 tests/data/toulouse_maelis/Q_update_family.xml create mode 100644 tests/data/toulouse_maelis/R_create_family.xml create mode 100644 tests/data/toulouse_maelis/R_create_family_error.xml create mode 100644 tests/data/toulouse_maelis/R_is_rl_exists.xml create mode 100644 tests/data/toulouse_maelis/R_update_coordinate.xml create mode 100644 tests/data/toulouse_maelis/R_update_family.xml create mode 100644 tests/data/toulouse_maelis/R_update_family_error.xml create mode 100644 tests/data/toulouse_maelis/R_update_family_soap_error.xml diff --git a/passerelle/contrib/toulouse_maelis/models.py b/passerelle/contrib/toulouse_maelis/models.py index dc7a591f..fd2a2cce 100644 --- a/passerelle/contrib/toulouse_maelis/models.py +++ b/passerelle/contrib/toulouse_maelis/models.py @@ -138,6 +138,14 @@ class ToulouseMaelis(BaseResource, HTTPResource): add_text_value('Sex', child, ['sexe']) return data + def replace_null_values(self, dico): + '''send null fields as empty SOAP tag to tell maelis to empty the value''' + for key, value in dico.items(): + if isinstance(value, dict): + self.replace_null_values(value) + if value is None: + dico[key] = '' + @endpoint( display_category=_('Family'), description='Liste des catégories', @@ -256,6 +264,100 @@ class ToulouseMaelis(BaseResource, HTTPResource): data = self.get_family(family_id) return {'data': data} + @endpoint( + display_category=_('Family'), + description="Informations sur un responsable légal", + perm='can_access', + name='read-rl', + parameters={ + 'NameID': {'description': _('Publik ID')}, + 'rl_id': {'description': 'Numéro du représentant légal'}, + }, + ) + def read_rl(self, request, NameID, rl_id): + family_id = self.get_link(NameID).family_id + data = self.get_family(family_id) + if data['RL1']['num'] == rl_id: + data = data['RL1'] + elif data['RL2'] and data['RL2']['num'] == rl_id: + data = data['RL2'] + else: + raise APIError("no '%s' RL on '%s' family" % (rl_id, family_id), err_code='not-found') + return {'data': data} + + @endpoint( + display_category=_('Family'), + description="Vérifier qu'un responsable légal existe en base", + perm='can_access', + name='is-rl-exists', + post={'request_body': {'schema': {'application/json': schemas.ISRLEXISTS_SCHEMA}}}, + ) + def is_rl_exists(self, request, post_data): + response = self.call('Family', 'isRLExists', **post_data) + return {'data': response} + + @endpoint( + display_category=_('Family'), + description='Création de la famille', + name='create-family', + perm='can_access', + parameters={'NameID': {'description': _('Publik ID')}}, + post={'request_body': {'schema': {'application/json': schemas.CREATE_FAMILY_SCHEMA}}}, + ) + def create_family(self, request, NameID, post_data): + if self.link_set.filter(name_id=NameID).exists(): + raise APIError('User already linked to family', err_code='already-linked') + + response = self.call('Family', 'createFamily', **post_data) + data = serialize_object(response) + family_id = data.get('number') + if not family_id: + errors = data.get('rl1ErrorList') + data.get('childErrorList') + err_codes = [x.split(':')[0][:4] for x in errors] + raise APIError(' ; '.join(errors), err_code=', '.join(err_codes)) + + Link.objects.create(resource=self, name_id=NameID, family_id=family_id) + return {'data': data} + + @endpoint( + display_category=_('Family'), + description='Modification de la famille', + name='update-family', + perm='can_access', + parameters={'NameID': {'description': _('Publik ID')}}, + post={'request_body': {'schema': {'application/json': schemas.UPDATE_FAMILY_SCHEMA}}}, + ) + def update_family(self, request, NameID, post_data): + family_id = self.get_link(NameID).family_id + self.replace_null_values(post_data) + + response = self.call('Family', 'updateFamily', dossierNumber=family_id, **post_data) + data = serialize_object(response) + family_id = data.get('number') + if not family_id: + errors = data.get('rl1ErrorList') + data.get('childErrorList') + err_codes = [x.split(':')[0][:4] for x in errors] + raise APIError(' ; '.join(errors), err_code=', '.join(err_codes)) + return {'data': data} + + @endpoint( + display_category=_('Family'), + description="Mise à jour des coordonnées d'une personne", + name='update-coordinate', + perm='can_access', + parameters={ + 'NameID': {'description': _('Publik ID')}, + 'rl_id': {'description': 'Numéro du représentant légal'}, + }, + post={'request_body': {'schema': {'application/json': schemas.UPDATE_COORDINATE_SCHEMA}}}, + ) + def update_coordinate(self, request, NameID, rl_id, post_data): + family_id = self.get_link(NameID).family_id + self.replace_null_values(post_data) + + self.call('Family', 'updateCoordinate', numDossier=family_id, numPerson=rl_id, **post_data) + return {'data': 'ok'} + class Link(models.Model): resource = models.ForeignKey(ToulouseMaelis, on_delete=models.CASCADE) diff --git a/passerelle/contrib/toulouse_maelis/schemas.py b/passerelle/contrib/toulouse_maelis/schemas.py index 2c61b4f5..19cc394d 100644 --- a/passerelle/contrib/toulouse_maelis/schemas.py +++ b/passerelle/contrib/toulouse_maelis/schemas.py @@ -14,6 +14,17 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import copy + +BOOLEAN_TYPES = [ + {'type': 'boolean'}, + { + 'type': 'string', + 'pattern': '^([Oo][Uu][Ii]|[Nn][Oo][Nn]|[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|1|0)$', + 'pattern_description': 'Les valeurs "0", "1", "true", "false", "oui" ou "non" sont autorisées (insensibles à la casse).', + }, +] + LINK_SCHEMA = { '$schema': 'http://json-schema.org/draft-04/schema#', 'title': 'Link', @@ -36,7 +47,279 @@ LINK_SCHEMA = { 'dateBirth': { 'description': 'Date de naissance du RL1', 'type': 'string', - 'pattern': '[0-9]{4}-[0-9]{2}-[0-9]{2}', + 'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$', }, }, } + +ISRLEXISTS_SCHEMA = { + '$schema': 'http://json-schema.org/draft-04/schema#', + 'title': 'Link', + 'description': "Appairage d'un usager Publik à une famille dans Maelis", + 'type': 'object', + 'required': ['firstname', 'lastname', 'datebirth'], + 'properties': { + 'firstname': { + 'description': 'Prénom', + 'type': 'string', + }, + 'lastname': { + 'description': 'Nom', + 'type': 'string', + }, + 'datebirth': { + 'description': 'Date de naissance', + 'type': 'string', + 'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$', + }, + }, +} + +ADDRESS_SCHEMA = { + '$schema': 'http://json-schema.org/draft-04/schema#', + 'title': 'Address', + 'description': 'Informations sur une adresse', + 'type': 'object', + 'required': ['street1', 'town', 'zipcode'], + 'properties': { + 'num': { + 'description': 'numéro', + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'numComp': { + 'description': 'Complément du numéro (B, T ou Q)', + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'street1': { + 'description': 'Libellé de la voie', + 'type': 'string', + }, + 'street2': { + 'description': 'Complément de la voie', + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'town': { + 'description': 'Ville', + 'type': 'string', + }, + 'zipcode': { + 'description': 'Code postal', + 'type': 'string', + }, + }, +} + +CONTACT_SCHEMA = { + '$schema': 'http://json-schema.org/draft-04/schema#', + 'title': 'Contact', + 'description': 'Informations sur le contact', + 'oneOf': [ + {'type': 'null'}, + { + 'type': 'object', + 'properties': { + 'phone': { + 'description': 'Téléphone', + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'mobile': { + 'description': 'Portable', + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'mail': { + 'description': 'Mail', + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'isContactMail': { + 'description': 'Accepte de recevoir des mails', + 'oneOf': BOOLEAN_TYPES, + }, + 'isContactSms': { + 'description': 'Accepte de recevoir des sms', + 'oneOf': BOOLEAN_TYPES, + }, + 'isInvoicePdf': { + 'description': 'Accepte de ne plus recevoir de facture papier', + 'oneOf': BOOLEAN_TYPES, + }, + }, + }, + ], +} + +ADDRESSPROF_SCHEMA = { + '$schema': 'http://json-schema.org/draft-04/schema#', + 'title': 'Contact', + 'description': "Informations sur l'adresse professionnelle", + 'oneOf': [ + {'type': 'null'}, + { + 'type': 'object', + 'properties': { + 'num': { + 'description': "Numéro de l'adresse", + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'street': { + 'description': 'Nom de la voie', + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'town': { + 'description': 'Ville', + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'zipcode': { + 'description': 'Code postal', + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + }, + }, + ], +} + +PROFESSION_SCHEMA = { + '$schema': 'http://json-schema.org/draft-04/schema#', + 'title': 'Profession', + 'description': 'Informations sur la profession', + 'oneOf': [ + {'type': 'null'}, + { + 'type': 'object', + 'properties': { + 'codeCSP': { + 'description': 'Catégorie socio-professionnelle', + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'profession': { + 'description': 'Profession', + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'employerName': { + 'description': "Nom de l'employeur", + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'phone': { + 'description': 'Téléphone', + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'addressPro': ADDRESSPROF_SCHEMA, + }, + }, + ], +} + +CAFINFO_SCHEMA = { + '$schema': 'http://json-schema.org/draft-04/schema#', + 'title': 'Contact', + 'description': 'Informations sur la CAF', + 'oneOf': [ + {'type': 'null'}, + { + 'type': 'object', + 'properties': { + 'number': { + 'description': "Numéro d'allocataire", + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'organ': { + 'description': "Nom de l'organisme", + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + }, + }, + ], +} + +RLINFO_SCHEMA = { + '$schema': 'http://json-schema.org/draft-04/schema#', + 'title': 'RLInfo', + 'description': "Informations sur le responsable légal", + 'type': 'object', + 'required': ['firstname', 'lastname', 'quality', 'dateBirth', 'adresse'], + 'properties': { + 'civility': { + 'description': 'civilité (depuis référenciel)', + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'firstname': { + 'description': 'Prénom', + 'type': 'string', + }, + 'lastname': { + 'description': 'Nom', + 'type': 'string', + }, + 'quality': { + 'description': 'Qualité', + 'type': 'string', + }, + 'dateBirth': { + 'description': 'Date de naissance', + 'type': 'string', + 'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$', + }, + 'adresse': ADDRESS_SCHEMA, + 'contact': CONTACT_SCHEMA, + 'profession': PROFESSION_SCHEMA, + 'CAFInfo': CAFINFO_SCHEMA, + }, +} + + +CREATE_FAMILY_SCHEMA = { + '$schema': 'http://json-schema.org/draft-04/schema#', + 'title': 'Family', + 'description': 'Informations pour créer ou mettre à jour une famille', + 'type': 'object', + 'required': ['rl1', 'categorie', 'situation'], + 'properties': { + 'categorie': { + 'description': 'Categorie (depuis référenciel)', + 'type': 'string', + }, + 'situation': { + 'description': 'Situation familiale (depuis référenciel)', + 'type': 'string', + }, + 'flagCom': { + 'description': 'Hors commune', + 'oneOf': BOOLEAN_TYPES, + }, + 'nbChild': { + 'description': "Nombre d'enfants à charge", + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'nbTotalChild': { + 'description': "Nombre total d'enfants", + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'nbAES': { + 'description': "Nombre d'AES", + 'oneOf': [{'type': 'null'}, {'type': 'string'}], + }, + 'rl1': RLINFO_SCHEMA, + 'rl2': RLINFO_SCHEMA, + }, + 'unflatten': True, +} + + +UPDATE_FAMILY_SCHEMA = copy.deepcopy(CREATE_FAMILY_SCHEMA) +UPDATE_FAMILY_SCHEMA['required'] = ['categorie', 'situation'] + + +# Schemas below describe parameters of Maelis wrapper around updateFamily endpoint + +UPDATE_COORDINATE_SCHEMA = { + '$schema': 'http://json-schema.org/draft-04/schema#', + 'title': 'Update coordinate', + 'description': "Mise à jour des coordonnées d'un responsable légal", + 'type': 'object', + 'properties': { + 'adresse': ADDRESS_SCHEMA, + 'contact': CONTACT_SCHEMA, + 'profession': PROFESSION_SCHEMA, + 'CAFInfo': CAFINFO_SCHEMA, + }, + 'unflatten': True, +} diff --git a/tests/data/toulouse_maelis/Q_create_family.xml b/tests/data/toulouse_maelis/Q_create_family.xml new file mode 100644 index 00000000..c52fa792 --- /dev/null +++ b/tests/data/toulouse_maelis/Q_create_family.xml @@ -0,0 +1,28 @@ + + + + + + maelis-webservice + maelis-password + + + + + + ACCEUI + C + + Doe + Jhon + AU + 1938-07-26 + + Chateau + Paris + 75014 + + + + + diff --git a/tests/data/toulouse_maelis/Q_update_coordinate.xml b/tests/data/toulouse_maelis/Q_update_coordinate.xml new file mode 100644 index 00000000..35d8c839 --- /dev/null +++ b/tests/data/toulouse_maelis/Q_update_coordinate.xml @@ -0,0 +1,25 @@ + + + + + + maelis-webservice + maelis-password + + + + + + 1312 + 613878 + + 169 + + Château + + Paris + 75014 + + + + diff --git a/tests/data/toulouse_maelis/Q_update_family.xml b/tests/data/toulouse_maelis/Q_update_family.xml new file mode 100644 index 00000000..698dcfde --- /dev/null +++ b/tests/data/toulouse_maelis/Q_update_family.xml @@ -0,0 +1,29 @@ + + + + + + maelis-webservice + maelis-password + + + + + + 1312 + BI + C + + Doe + Jhon + AU + 1938-07-26 + + Chateau + Paris + 75014 + + + + + diff --git a/tests/data/toulouse_maelis/R_create_family.xml b/tests/data/toulouse_maelis/R_create_family.xml new file mode 100644 index 00000000..0dad2af5 --- /dev/null +++ b/tests/data/toulouse_maelis/R_create_family.xml @@ -0,0 +1,11 @@ + + + + + + 196545 + 394634V2 + + + + diff --git a/tests/data/toulouse_maelis/R_create_family_error.xml b/tests/data/toulouse_maelis/R_create_family_error.xml new file mode 100644 index 00000000..90536154 --- /dev/null +++ b/tests/data/toulouse_maelis/R_create_family_error.xml @@ -0,0 +1,11 @@ + + + + + + 0 + E54a : Il existe déjà un Responsable Légal correspondant au nom [DOE], prénom [JHON], date de naissance [26/07/1938] - Personne n°[613955] - Famille n°[196544] + + + + diff --git a/tests/data/toulouse_maelis/R_is_rl_exists.xml b/tests/data/toulouse_maelis/R_is_rl_exists.xml new file mode 100644 index 00000000..d93cfcec --- /dev/null +++ b/tests/data/toulouse_maelis/R_is_rl_exists.xml @@ -0,0 +1,8 @@ + + + + + %s + + + diff --git a/tests/data/toulouse_maelis/R_update_coordinate.xml b/tests/data/toulouse_maelis/R_update_coordinate.xml new file mode 100644 index 00000000..6d1db10e --- /dev/null +++ b/tests/data/toulouse_maelis/R_update_coordinate.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/data/toulouse_maelis/R_update_family.xml b/tests/data/toulouse_maelis/R_update_family.xml new file mode 100644 index 00000000..58a52a55 --- /dev/null +++ b/tests/data/toulouse_maelis/R_update_family.xml @@ -0,0 +1,32 @@ + + + + + + 196544 + BI + C + + 613955 + DOE + JHON + AU + MR + 1938-07-26T00:00:00+01:00 + + + 0 + Chateau + Paris + 75014 + + + false + false + false + + + + + + diff --git a/tests/data/toulouse_maelis/R_update_family_error.xml b/tests/data/toulouse_maelis/R_update_family_error.xml new file mode 100644 index 00000000..23d4b59b --- /dev/null +++ b/tests/data/toulouse_maelis/R_update_family_error.xml @@ -0,0 +1,16 @@ + + + + + + 0 + + + + + + E65a : Il existe déjà un enfant correspondant au nom [ZIMMERMAN], prénom [ROBERT], date de naissance [24/05/1941] - Personne n°[614051] - Famille n°[196544] + + + + diff --git a/tests/data/toulouse_maelis/R_update_family_soap_error.xml b/tests/data/toulouse_maelis/R_update_family_soap_error.xml new file mode 100644 index 00000000..35842c00 --- /dev/null +++ b/tests/data/toulouse_maelis/R_update_family_soap_error.xml @@ -0,0 +1,16 @@ + + + + + soap:Server + Une erreur est survenue : java.sql.SQLDataException: ORA-01438: valeur incohérente avec la précision indiquée pour cette colonne + + + + Une erreur est survenue : java.sql.SQLDataException: ORA-01438: valeur incohérente avec la précision indiquée pour cette colonne + + + + + + diff --git a/tests/test_toulouse_maelis.py b/tests/test_toulouse_maelis.py index feb73ff9..42bb8bd9 100644 --- a/tests/test_toulouse_maelis.py +++ b/tests/test_toulouse_maelis.py @@ -19,6 +19,7 @@ import os import mock import pytest +from lxml import etree from requests.exceptions import ConnectionError from passerelle.contrib.toulouse_maelis.models import Link, ToulouseMaelis @@ -46,6 +47,19 @@ READ_CIVILITIES = FakedResponse(content=get_xml_file('R_read_civility_list.xml') READ_CSP = FakedResponse(content=get_xml_file('R_read_csp_list.xml'), status_code=200) READ_QUALITIES = FakedResponse(content=get_xml_file('R_read_quality_list.xml'), status_code=200) READ_SITUATIONS = FakedResponse(content=get_xml_file('R_read_situation_list.xml'), status_code=200) +IS_RL_EXISTS_TRUE = FakedResponse(content=get_xml_file('R_is_rl_exists.xml') % b'true', status_code=200) +IS_RL_EXISTS_FALSE = FakedResponse(content=get_xml_file('R_is_rl_exists.xml') % b'false', status_code=200) +CREATE_FAMILY = FakedResponse(content=get_xml_file('R_create_family.xml'), status_code=200) +CREATE_FAMILY_ERR = FakedResponse(content=get_xml_file('R_create_family_error.xml'), status_code=200) +UPDATE_FAMILY = FakedResponse(content=get_xml_file('R_update_family.xml'), status_code=200) +UPDATE_FAMILY_ERR = FakedResponse(content=get_xml_file('R_update_family_error.xml'), status_code=200) +UPDATE_FAMILY_500 = FakedResponse(content=get_xml_file('R_update_family_soap_error.xml'), status_code=500) + + +def assert_sent_payload(mocked_post, query_file): + soap_sent = etree.tostring(etree.fromstring(mocked_post.call_args.kwargs['data']), pretty_print=True) + expected = etree.tostring(etree.fromstring(get_xml_file(query_file)), pretty_print=True) + assert soap_sent.decode() == expected.decode() def get_endpoint(name): @@ -126,6 +140,30 @@ def test_check_status(mocked_post, mocked_get, get_responses, post_responses, ex con.check_status() +def test_replace_null_values(con): + payload = { + 'adresse': { + 'num': '169', + 'numComp': None, + 'street1': 'Chateau', + 'street2': None, + 'town': 'Paris', + 'zipcode': '75014', + } + } + con.replace_null_values(payload) + assert payload == { + 'adresse': { + 'num': '169', + 'numComp': '', + 'street1': 'Chateau', + 'street2': '', + 'town': 'Paris', + 'zipcode': '75014', + } + } + + @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_link(mocked_post, mocked_get, con, app): @@ -420,3 +458,316 @@ def test_read_family_not_linked_error(con, app): resp = app.get(url + '?NameID=') assert resp.json['err'] == 'not-linked' assert resp.json['err_desc'] == 'User not linked to family' + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_read_rl1(mocked_post, mocked_get, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.side_effect = [READ_FAMILY, READ_CATEGORIES, READ_SITUATIONS, READ_CIVILITIES, READ_QUALITIES] + url = get_endpoint('read-rl') + Link.objects.create(resource=con, family_id='1312', name_id='local') + + resp = app.get(url + '?NameID=local&rl_id=613878') + assert resp.json['err'] == 0 + assert resp.json['data']['firstname'] == 'DAMIEN' + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_read_rl2(mocked_post, mocked_get, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.side_effect = [READ_FAMILY, READ_CATEGORIES, READ_SITUATIONS, READ_CIVILITIES, READ_QUALITIES] + url = get_endpoint('read-rl') + Link.objects.create(resource=con, family_id='1312', name_id='local') + + resp = app.get(url + '?NameID=local&rl_id=613879') + assert resp.json['err'] == 0 + assert resp.json['data'] == { + 'num': '613879', + 'lastname': 'COSTANZE', + 'firstname': 'JENNIFER', + 'quality': 'MERE', + 'civility': 'MME', + 'dateBirth': '1987-05-21T00:00:00+02:00', + 'adresse': { + 'idStreet': 'AV0044', + 'num': 9, + 'numComp': None, + 'street1': 'AVENUE VALDILETTA', + 'street2': 'LES MANDARINIERS', + 'town': 'NICE', + 'zipcode': '06100', + }, + 'contact': { + 'phone': None, + 'mobile': None, + 'mail': None, + 'isContactMail': False, + 'isContactSms': False, + 'isInvoicePdf': False, + }, + 'profession': None, + 'CAFInfo': {'number': '51', 'organ': None}, + 'civility_text': 'Madame', + 'quality_text': 'MERE', + } + + +def test_read_rl_not_linked_error(con, app): + url = get_endpoint('read-rl') + + resp = app.get(url + '?NameID=local&rl_id=613879') + assert resp.json['err'] == 'not-linked' + assert resp.json['err_desc'] == 'User not linked to family' + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_read_rl_not_found(mocked_post, mocked_get, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.side_effect = [READ_FAMILY, READ_CATEGORIES, READ_SITUATIONS, READ_CIVILITIES, READ_QUALITIES] + url = get_endpoint('read-rl') + Link.objects.create(resource=con, family_id='1312', name_id='local') + + resp = app.get(url + '?NameID=local&rl_id=000000') + assert resp.json['err'] == 'not-found' + assert resp.json['err_desc'] == "no '000000' RL on '1312' family" + + +@pytest.mark.parametrize( + 'post_response, result', + [ + (IS_RL_EXISTS_TRUE, True), + (IS_RL_EXISTS_FALSE, False), + ], +) +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_is_rl_exists(mocked_post, mocked_get, post_response, result, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = post_response + url = get_endpoint('is-rl-exists') + + params = { + 'firstname': 'Damien', + 'lastname': 'Costanze', + 'datebirth': '1980-10-07', + } + resp = app.post_json(url, params=params) + assert resp.json['err'] == 0 + assert resp.json['data'] == result + + +def test_is_rl_exists_schema_error(con, app): + url = get_endpoint('is-rl-exists') + + params = { + 'firstname': 'Damien', + 'lastname': 'Costanze', + 'datebirth': '1980-10-07 more text', + } + resp = app.post_json(url, params=params, status=400) + assert resp.json['err'] == 1 + assert "does not match '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'" in resp.json['err_desc'] + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_create_family(mocked_post, mocked_get, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = CREATE_FAMILY + url = get_endpoint('create-family') + params = { + 'categorie': 'ACCEUI', + 'situation': 'C', + 'rl1/firstname': 'Jhon', + 'rl1/lastname': 'Doe', + 'rl1/quality': 'AU', + 'rl1/dateBirth': '1938-07-26', + 'rl1/adresse/street1': 'Chateau', + 'rl1/adresse/town': 'Paris', + 'rl1/adresse/zipcode': '75014', + } + + resp = app.post_json(url + '?NameID=local', params=params) + assert_sent_payload(mocked_post, 'Q_create_family.xml') + assert resp.json['err'] == 0 + assert resp.json['data'] == { + 'number': 196545, + 'password': '394634V2', + 'rl1ErrorList': [], + 'childErrorList': [], + } + assert Link.objects.get(resource=con, family_id='196545', name_id='local') + + +def test_create_family_already_linked_error(con, app): + url = get_endpoint('create-family') + params = { + 'categorie': 'ACCEUI', + 'situation': 'C', + 'rl1/firstname': 'Jhon', + 'rl1/lastname': 'Doe', + 'rl1/quality': 'AU', + 'rl1/dateBirth': '1938-07-26', + 'rl1/adresse/street1': 'Chateau', + 'rl1/adresse/town': 'Paris', + 'rl1/adresse/zipcode': '75014', + } + + Link.objects.create(resource=con, family_id='1312', name_id='local') + resp = app.post_json(url + '?NameID=local', params=params) + assert resp.json['err'] == 'already-linked' + assert resp.json['err_desc'] == 'User already linked to family' + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_create_family_maelis_error(mocked_post, mocked_get, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = CREATE_FAMILY_ERR + url = get_endpoint('create-family') + params = { + 'categorie': 'ACCEUI', + 'situation': 'C', + 'rl1/firstname': 'Jhon', + 'rl1/lastname': 'Doe', + 'rl1/quality': 'AU', + 'rl1/dateBirth': '1938-07-26', + 'rl1/adresse/street1': 'Chateau', + 'rl1/adresse/town': 'Paris', + 'rl1/adresse/zipcode': '75014', + } + + resp = app.post_json(url + '?NameID=local', params=params) + assert resp.json['err'] == 'E54a' + assert 'Il existe déjà' in resp.json['err_desc'] + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_update_family(mocked_post, mocked_get, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = UPDATE_FAMILY + url = get_endpoint('update-family') + params = { + 'categorie': 'BI', + 'situation': 'C', + 'rl1/firstname': 'Jhon', + 'rl1/lastname': 'Doe', + 'rl1/quality': 'AU', + 'rl1/dateBirth': '1938-07-26', + 'rl1/adresse/street1': 'Chateau', + 'rl1/adresse/town': 'Paris', + 'rl1/adresse/zipcode': '75014', + } + + Link.objects.create(resource=con, family_id='1312', name_id='local') + resp = app.post_json(url + '?NameID=local', params=params) + assert_sent_payload(mocked_post, 'Q_update_family.xml') + assert resp.json['err'] == 0 + + +def test_update_family_already_not_linked_error(con, app): + url = get_endpoint('update-family') + params = { + 'categorie': 'BI', + 'situation': 'C', + 'rl1/firstname': 'Jhon', + 'rl1/lastname': 'Doe', + 'rl1/quality': 'AU', + 'rl1/dateBirth': '1938-07-26', + 'rl1/adresse/street1': 'Chateau', + 'rl1/adresse/town': 'Paris', + 'rl1/adresse/zipcode': '75014', + } + + resp = app.post_json(url + '?NameID=local', params=params) + assert resp.json['err'] == 'not-linked' + assert resp.json['err_desc'] == 'User not linked to family' + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_update_family_maelis_error(mocked_post, mocked_get, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = UPDATE_FAMILY_ERR + url = get_endpoint('update-family') + params = { + 'categorie': 'ACCEUI', + 'situation': 'C', + 'childList/0/lastname': 'Zimmerman', + 'childList/0/firstname': 'Robert', + 'childList/0/sexe': 'M', + 'childList/0/birth/dateBirth': '1941-05-24', + 'childList/0/birth/place': 'Duluth', + } + + Link.objects.create(resource=con, family_id='1312', name_id='local') + # get 500 because error repsonse is wrongly formatted + resp = app.post_json(url + '?NameID=local', params=params, status=500) + assert resp.json == { + 'err': 1, + 'err_class': 'zeep.exceptions.XMLParseError', + 'err_desc': "Unexpected element 'adresse', expected 'lastname'", + 'data': None, + } + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_update_family_soap_error(mocked_post, mocked_get, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = UPDATE_FAMILY_500 + url = get_endpoint('update-family') + params = { + 'nbChild': '100', + 'categorie': 'BI', + 'situation': 'C', + 'rl1/firstname': 'Jhon', + 'rl1/lastname': 'Doe', + 'rl1/quality': 'AU', + 'rl1/dateBirth': '1938-07-26', + 'rl1/adresse/street1': 'Chateau', + 'rl1/adresse/town': 'Paris', + 'rl1/adresse/zipcode': '75014', + } + + Link.objects.create(resource=con, family_id='1312', name_id='local') + resp = app.post_json(url + '?NameID=local', params=params) + assert resp.json['err'] == 'Family-updateFamily-soap:Server' + assert 'Une erreur est survenue' in resp.json['err_desc'] + + +@mock.patch('passerelle.utils.Request.get') +@mock.patch('passerelle.utils.Request.post') +def test_update_coordinate(mocked_post, mocked_get, con, app): + mocked_get.return_value = FAMILY_SERVICE_WSDL + mocked_post.return_value = UPDATE_FAMILY + url = get_endpoint('update-coordinate') + params = { + 'adresse/num': '169', + 'adresse/numComp': None, + 'adresse/street1': 'Château', + 'adresse/street2': None, + 'adresse/town': 'Paris', + 'adresse/zipcode': '75014', + } + + Link.objects.create(resource=con, family_id='1312', name_id='local') + resp = app.post_json(url + '?NameID=local&rl_id=613878', params=params) + assert_sent_payload(mocked_post, 'Q_update_coordinate.xml') + assert resp.json['err'] == 0 + assert resp.json['data'] == 'ok' + + +def test_update_coordinate_schema_error(con, app): + url = get_endpoint('update-coordinate') + params = { + 'contact/isContactMail': 'true more text', + } + + resp = app.post_json(url + '?NameID=local&rl_id=613878', params=params, status=400) + assert resp.json['err'] == 1 + assert resp.json['err_desc'] == "'true more text' is not of type 'boolean'"