# -*- coding: utf-8 -*- # Copyright (C) 2022 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 . import logging import os import mock import pytest from requests.exceptions import ConnectionError from passerelle.contrib.toulouse_maelis.models import Link, ToulouseMaelis from passerelle.utils.jsonresponse import APIError from passerelle.utils.soap import SOAPError from tests.utils import FakedResponse, generic_endpoint_url, setup_access_rights TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'data', 'toulouse_maelis') def get_xml_file(filename): with open(os.path.join(TEST_BASE_DIR, filename), 'rb') as desc: return desc.read() CONNECTION_ERROR = ConnectionError('No address associated with hostname') FAMILY_SERVICE_WSDL = FakedResponse(content=get_xml_file('FamilyService.wsdl'), status_code=200) ACTIVITY_SERVICE_WSDL = FakedResponse(content=get_xml_file('ActivityService.wsdl'), status_code=200) FAILED_AUTH = FakedResponse(content=get_xml_file('R_failed_authentication.xml'), status_code=500) ISWSRUNNING_TRUE = FakedResponse(content=get_xml_file('R_is_ws_running.xml') % b'true', status_code=200) ISWSRUNNING_FALSE = FakedResponse(content=get_xml_file('R_is_ws_running.xml') % b'false', status_code=200) READ_FAMILY = FakedResponse(content=get_xml_file('R_read_family.xml'), status_code=200) READ_CATEGORIES = FakedResponse(content=get_xml_file('R_read_category_list.xml'), status_code=200) READ_CIVILITIES = FakedResponse(content=get_xml_file('R_read_civility_list.xml'), status_code=200) 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) def get_endpoint(name): url = generic_endpoint_url('toulouse-maelis', name) assert url == '/toulouse-maelis/test/%s' % name return url @pytest.fixture def con(db): return setup_access_rights( ToulouseMaelis.objects.create( slug='test', zeep_wsse_username='maelis-webservice', zeep_wsse_password='maelis-password', ) ) @mock.patch('passerelle.utils.Request.get') def test_call_with_wrong_wsdl_url(mocked_get, con): mocked_get.side_effect = CONNECTION_ERROR with pytest.raises( SOAPError, match='cannot be loaded: No address associated with hostname', ) as e: con.call('Family', 'isWSRunning') assert e.value.err == 1 assert e.value.http_status == 200 assert e.value.url == 'https://demo-toulouse.sigec.fr/maelisws-toulouse/services/FamilyService?wsdl' @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_call_with_wrong_credentials(mocked_post, mocked_get, con): mocked_get.side_effect = [FAMILY_SERVICE_WSDL] mocked_post.side_effect = [FAILED_AUTH] with pytest.raises( APIError, match='The security token could not be authenticated or authorized', ) as e: con.call('Family', 'isWSRunning') assert e.value.__dict__ == { 'err': 1, 'log_error': False, 'http_status': 200, 'err_code': 'Family-isWSRunning-ns1:FailedAuthentication', } @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_call(mocked_post, mocked_get, con): mocked_get.side_effect = [FAMILY_SERVICE_WSDL] mocked_post.side_effect = [ISWSRUNNING_TRUE] resp = con.call('Family', 'isWSRunning') assert resp is True @pytest.mark.parametrize( 'get_responses, post_responses, exception', [ ([FAMILY_SERVICE_WSDL, ACTIVITY_SERVICE_WSDL], [ISWSRUNNING_TRUE, ISWSRUNNING_TRUE], None), ([FAMILY_SERVICE_WSDL, CONNECTION_ERROR], [ISWSRUNNING_TRUE, ISWSRUNNING_TRUE], SOAPError), ([FAMILY_SERVICE_WSDL, ACTIVITY_SERVICE_WSDL], [ISWSRUNNING_TRUE, FAILED_AUTH], APIError), ([FAMILY_SERVICE_WSDL, ACTIVITY_SERVICE_WSDL], [ISWSRUNNING_TRUE, ISWSRUNNING_FALSE], AssertionError), ], ) @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_check_status(mocked_post, mocked_get, get_responses, post_responses, exception, con): mocked_get.side_effect = get_responses mocked_post.side_effect = post_responses if exception: with pytest.raises(Exception): con.check_status() else: con.check_status() @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_link(mocked_post, mocked_get, con, app): mocked_get.return_value = FAMILY_SERVICE_WSDL mocked_post.return_value = READ_FAMILY url = get_endpoint('link') assert Link.objects.count() == 0 params = { 'family_id': '1312', 'firstname': 'Damien', 'lastname': 'Costanze', 'dateBirth': '1980-10-07', } resp = app.post_json(url + '?NameID=local', params=params) assert Link.objects.count() == 1 assert resp.json['err'] == 0 assert resp.json['data'] == 'ok' params['lastname'] = 'John' resp = app.post_json(url + '?NameID=local', params=params) assert Link.objects.count() == 1 assert resp.json['err'] == 'not-found' assert resp.json['err_desc'] == "RL1 does not match '1312' family" def test_unlink(con, app): url = get_endpoint('unlink') Link.objects.create(resource=con, family_id='1312', name_id='local') resp = app.post_json(url + '?NameID=local') assert Link.objects.count() == 0 assert resp.json['err'] == 0 assert resp.json['data'] == 'ok' resp = app.post_json(url + '?NameID=local') 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_get_referential_using_cache(mocked_post, mocked_get, con, freezer): mocked_get.return_value = FAMILY_SERVICE_WSDL mocked_post.return_value = READ_CATEGORIES freezer.move_to('2021-11-10 00:00') con.get_referential('Category') assert mocked_post.call_count == 1 con.get_referential('Category') assert mocked_post.call_count == 1 freezer.move_to('2021-11-10 02:00') con.get_referential('Category') assert mocked_post.call_count == 2 @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_get_referential_value(mocked_post, mocked_get, con): mocked_get.return_value = FAMILY_SERVICE_WSDL mocked_post.return_value = READ_CSP assert con.get_referential_value('CSP', '1') == 'AGRICULTEUR' assert con.get_referential_value('CSP', 'AGR') == 'AGRICULTEUR' @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_get_referential_value_not_found(mocked_post, mocked_get, con, caplog): mocked_get.return_value = FAMILY_SERVICE_WSDL mocked_post.return_value = READ_CIVILITIES assert con.get_referential_value('Civility', 'MR') == 'MR' assert len(caplog.records) == 1 assert caplog.records[0].levelno == logging.WARNING assert caplog.records[0].message == "No 'MR' key into Maelis 'Civility' referential" @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_read_category_list(mocked_post, mocked_get, con, app): mocked_get.return_value = FAMILY_SERVICE_WSDL mocked_post.return_value = READ_CATEGORIES url = get_endpoint('read-category-list') resp = app.get(url) assert resp.json['err'] == 0 assert resp.json['data'] == [ {'id': 'BI', 'text': 'BIPARENTALE'}, {'id': 'ACCEUI', 'text': "FAMILLE D'ACCUEIL"}, {'id': 'MONO', 'text': 'MONOPARENTALE'}, ] @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_read_civility_list(mocked_post, mocked_get, con, app): mocked_get.return_value = FAMILY_SERVICE_WSDL mocked_post.return_value = READ_CIVILITIES url = get_endpoint('read-civility-list') resp = app.get(url) assert resp.json['err'] == 0 assert resp.json['data'] == [ {"id": "MME", "text": "Madame"}, {"id": "M.", "text": "Monsieur"}, ] def test_read_complement_list(con, app): url = get_endpoint('read-complement-list') resp = app.get(url) assert resp.json['err'] == 0 assert resp.json['data'] == [ {'id': 'B', 'text': 'bis'}, {'id': 'T', 'text': 'ter'}, {'id': 'Q', 'text': 'quater'}, ] @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_read_csp_list(mocked_post, mocked_get, con, app): mocked_get.return_value = FAMILY_SERVICE_WSDL mocked_post.return_value = READ_CSP url = get_endpoint('read-csp-list') resp = app.get(url) assert resp.json['err'] == 0 assert resp.json['data'] == [ {'id': '14', 'text': 'AGENT DE MAITRISE'}, {'id': '1', 'text': 'AGRICULTEUR'}, {'id': 'ARTI', 'text': 'ARTISAN'}, {'id': '2', 'text': 'ARTISAN-COMMERCANT'}, {'id': '15', 'text': 'AUTRES'}, {'id': 'CADR', 'text': 'CADRE'}, {'id': '13', 'text': 'CADRE SUPERIEUR'}, {'id': '3', 'text': "CHEF D'ENTREPRISE"}, {'id': 'CHOM', 'text': 'CHOMEUR'}, {'id': 'COM', 'text': 'COMMERCANT'}, {'id': '10', 'text': "DEMANDEUR D'EMPLOI"}, {'id': 'DIV', 'text': 'DIVERS'}, {'id': 'EMP', 'text': 'EMPLOYE'}, {'id': 'ENS', 'text': 'ENSEIGNANT'}, {'id': 'ETU', 'text': 'ETUDIANT'}, {'id': '11', 'text': 'FONCTIONNAIRE'}, {'id': 'MAIR', 'text': 'MAIRIE DE NICE'}, {'id': 'OUV', 'text': 'OUVRIER'}, {'id': 'PERENS', 'text': 'PERISCO ENSEIGNANT'}, {'id': 'PEREXT', 'text': 'PERISCO EXTERNE'}, {'id': 'PERMAI', 'text': 'PERISCO MAIRIE DE NICE'}, {'id': 'PERANI', 'text': 'PERISCO S.ANIMATION'}, {'id': '9', 'text': 'PROFESSION LIBERALE'}, {'id': '12', 'text': 'RETRAITE'}, {'id': 'RMI', 'text': "REVENU MINIMUM D'INSERTION"}, {'id': '16', 'text': 'SANS PROFESSION'}, {'id': 'SEC', 'text': 'SECRETAIRE'}, ] @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_read_quality_list(mocked_post, mocked_get, con, app): mocked_get.return_value = FAMILY_SERVICE_WSDL mocked_post.return_value = READ_QUALITIES url = get_endpoint('read-quality-list') resp = app.get(url) assert resp.json['err'] == 0 assert resp.json['data'] == [ {'id': 'AU', 'text': 'AUTRE'}, {'id': 'BP', 'text': 'BEAU PERE'}, {'id': 'BM', 'text': 'BELLE MERE'}, {'id': 'CONSO', 'text': 'CONSOMMATEUR'}, {'id': 'EN', 'text': 'ENFANT'}, {'id': 'FS', 'text': 'FRERES ET SOEURS'}, {'id': 'GM', 'text': 'GRAND MERE MATERNELLE'}, {'id': 'GP', 'text': 'GRAND PERE MATERNEL'}, {'id': 'GMP', 'text': 'GRAND-MERE PATERNELLE'}, {'id': 'GPP', 'text': 'GRAND-PERE PATERNEL'}, {'id': 'MAIRIE', 'text': 'MAIRIE'}, {'id': 'MERE', 'text': 'MERE'}, {'id': 'O', 'text': 'ONCLE'}, {'id': 'OS', 'text': 'ORGANISME SOCIAL'}, {'id': 'PERE', 'text': 'PERE'}, {'id': 'T', 'text': 'TANTE'}, {'id': 'TUTEUR', 'text': 'TUTEUR'}, ] def test_read_sex_list(con, app): url = get_endpoint('read-sex-list') resp = app.get(url) assert resp.json['err'] == 0 assert resp.json['data'] == [ {'id': 'M', 'text': 'Masculin'}, {'id': 'F', 'text': 'Féminin'}, ] @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_read_situation_list(mocked_post, mocked_get, con, app): mocked_get.return_value = FAMILY_SERVICE_WSDL mocked_post.return_value = READ_SITUATIONS url = get_endpoint('read-situation-list') resp = app.get(url) assert resp.json['err'] == 0 assert resp.json['data'] == [ {'id': 'C', 'text': 'Célibataire'}, {'id': 'D', 'text': 'Divorcé (e)'}, {'id': 'CS', 'text': 'EN COURS DE SEPARATION'}, {'id': 'M', 'text': 'Marié (e)'}, {'id': 'P', 'text': 'Pacsé (e)'}, {'id': 'S', 'text': 'Séparé (e)'}, {'id': 'UL', 'text': 'UNION LIBRE'}, {'id': 'V', 'text': 'Veuf (ve)'}, {'id': 'VM', 'text': 'Vivant maritalement'}, ] @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_read_family(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-family') Link.objects.create(resource=con, family_id='1312', name_id='local') resp = app.get(url + '?NameID=local') assert resp.json['err'] == 0 assert resp.json['data']['RL1'] == { 'num': '613878', 'lastname': 'COSTANZE', 'firstname': 'DAMIEN', 'quality': 'PERE', 'quality_text': 'PERE', 'civility': 'M.', 'civility_text': 'Monsieur', 'dateBirth': '1980-10-07T00:00:00+01:00', 'adresse': { 'idStreet': 'AV0044', 'num': 9, 'numComp': None, 'street1': 'AVENUE VALDILETTA', 'street2': 'LES MANDARINIERS', 'town': 'NICE', 'zipcode': '06100', }, 'contact': { 'phone': '0664107085', 'mobile': '0637957391', 'mail': 'petro.costache@yahoo.com', 'isContactMail': True, 'isContactSms': True, 'isInvoicePdf': True, }, 'profession': None, 'CAFInfo': {'number': '51', 'organ': None}, } assert resp.json['data']['childList'][0] == { 'num': '613880', 'lastname': 'COSTANZE', 'firstname': 'CASSANDRA', 'sexe': 'F', 'sexe_text': 'Féminin', 'birth': {'dateBirth': '2021-06-22T00:00:00+02:00', 'place': None}, 'dietcode': 'STD', 'fsl': None, 'bPhoto': False, 'bLeaveAlone': False, 'authorizedPersonList': [], 'indicatorList': [], 'medicalRecord': None, 'subscribeSchoolList': [], 'mother': None, 'father': None, 'rl': None, 'subscribeActivityList': [], 'paiInfoBean': None, } def test_read_family_not_linked_error(con, app): url = get_endpoint('read-family') resp = app.get(url + '?NameID=') assert resp.json['err'] == 'not-linked' assert resp.json['err_desc'] == 'User not linked to family'