From 09c544e56bee443437d83ca01491c13db2244ba1 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Sat, 16 Jul 2022 19:53:40 +0200
Subject: [PATCH] toulouse_maelis: add webservices to manage persons (#67327)

---
 passerelle/contrib/toulouse_maelis/models.py  | 145 ++++++++-
 passerelle/contrib/toulouse_maelis/schemas.py |  95 ++++++
 .../data/toulouse_maelis/Q_create_person.xml  |  42 +++
 .../data/toulouse_maelis/Q_delete_person.xml  |  19 ++
 .../data/toulouse_maelis/Q_update_person.xml  |  33 +++
 tests/data/toulouse_maelis/R_read_family.xml  |  20 ++
 tests/test_toulouse_maelis.py                 | 275 ++++++++++++++++++
 7 files changed, 628 insertions(+), 1 deletion(-)
 create mode 100644 tests/data/toulouse_maelis/Q_create_person.xml
 create mode 100644 tests/data/toulouse_maelis/Q_delete_person.xml
 create mode 100644 tests/data/toulouse_maelis/Q_update_person.xml

diff --git a/passerelle/contrib/toulouse_maelis/models.py b/passerelle/contrib/toulouse_maelis/models.py
index 27962ca2..be8d6afe 100644
--- a/passerelle/contrib/toulouse_maelis/models.py
+++ b/passerelle/contrib/toulouse_maelis/models.py
@@ -108,20 +108,31 @@ class ToulouseMaelis(BaseResource, HTTPResource):
             return key
 
     def get_link(self, NameID):
         try:
             return self.link_set.get(name_id=NameID)
         except Link.DoesNotExist:
             raise APIError('User not linked to family', err_code='not-linked')
 
-    def get_family(self, family_id):
+    def get_family_raw(self, family_id):
         response = self.call('Family', 'readFamily', dossierNumber=family_id)
         data = serialize_object(response)
 
+        # make emergencyPersonList keys egal to authorizedPersonList
+        for person in data['emergencyPersonList']:
+            person['numPerson'] = person['num']
+            person['dateBirth'] = '1970-01-01'  # I need a date
+            del person['num']
+            del person['id']
+        return data
+
+    def get_family(self, family_id):
+        data = self.get_family_raw(family_id)
+
         def add_text_value(referential_name, data, keys):
             last_key = keys.pop()
             for key in keys:
                 if not isinstance(data, dict) or not key in data:
                     return
                 data = data[key]
             if isinstance(data, dict) and last_key in data and data[last_key] is not None:
                 data[last_key + '_text'] = self.get_referential_value(referential_name, data[last_key])
@@ -131,16 +142,20 @@ class ToulouseMaelis(BaseResource, HTTPResource):
         add_text_value('Situation', data, ['situation'])
         for rlg in 'RL1', 'RL2':
             add_text_value('Civility', data, [rlg, 'civility'])
             add_text_value('Quality', data, [rlg, 'quality'])
             add_text_value('Complement', data, [rlg, 'adresse', 'numComp'])
             add_text_value('CSP', data, [rlg, 'profession', 'codeCSP'])
         for child in data['childList']:
             add_text_value('Sex', child, ['sexe'])
+        for kind in ('authorized', 'emergency'):
+            for person in data[kind + 'PersonList']:
+                add_text_value('Civility', person, ['civility'])
+                add_text_value('Quality', person, ['quality'])
         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:
@@ -280,16 +295,41 @@ class ToulouseMaelis(BaseResource, HTTPResource):
         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="Informations une personne authorisée à récupérer les enfants ou à prévenir en cas d'urgence",
+        perm='can_access',
+        name='read-person',
+        parameters={
+            'NameID': {'description': _('Publik ID')},
+            'person_id': {'description': 'Numéro de la personne'},
+            'kind': {'description': "'authorized' (par defaut) ou 'emergency'"},
+        },
+    )
+    def read_person(self, request, NameID, person_id, kind='authorized'):
+        if kind not in ('authorized', 'emergency'):
+            raise APIError("wrong '%s' value for kind parameter" % kind)
+        family_id = self.get_link(NameID).family_id
+        data = self.get_family(family_id)
+        for person in data[kind + 'PersonList']:
+            if str(person['numPerson']) == person_id:
+                break
+        else:
+            raise APIError(
+                "no '%s' %s person on '%s' family" % (person_id, kind, family_id), err_code='not-found'
+            )
+        return {'data': person}
+
     @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):
@@ -353,16 +393,119 @@ class ToulouseMaelis(BaseResource, HTTPResource):
     )
     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'}
 
+    @endpoint(
+        display_category=_('Family'),
+        description="Création d'une personne authorisée à récupérer les enfants ou à prévenir en cas d'urgence",
+        name='create-person',
+        perm='can_access',
+        parameters={
+            'NameID': {'description': _('Publik ID')},
+            'kind': {'description': "'authorized' (par defaut) ou 'emergency'"},
+        },
+        post={'request_body': {'schema': {'application/json': schemas.FAMILYPERSON_SCHEMA}}},
+    )
+    def create_person(self, request, NameID, post_data, kind='authorized'):
+        if kind not in ('authorized', 'emergency'):
+            raise APIError("wrong '%s' value for kind parameter" % kind)
+        family_id = self.get_link(NameID).family_id
+        family = self.get_family_raw(family_id)
+        self.replace_null_values(post_data)
+
+        personList = family[kind + 'PersonList']
+        personList.append(post_data)
+        payload = {
+            'dossierNumber': family_id,
+            'categorie': family['category'],
+            'situation': family['situation'],
+            kind + 'PersonList': [{'personList': personList}],
+        }
+        self.call('Family', 'updateFamily', **payload)
+        return {'data': 'ok'}
+
+    @endpoint(
+        display_category=_('Family'),
+        description="Mise à jour d'une personne authorisée à récupérer les enfants ou à prévenir en cas d'urgence",
+        name='update-person',
+        perm='can_access',
+        parameters={
+            'NameID': {'description': _('Publik ID')},
+            'person_id': {'description': 'Numéro de la personne'},
+            'kind': {'description': "'authorized' (par defaut) ou 'emergency'"},
+        },
+        post={'request_body': {'schema': {'application/json': schemas.FAMILYPERSON_SCHEMA}}},
+    )
+    def update_person(self, request, NameID, person_id, post_data, kind='authorized'):
+        if kind not in ('authorized', 'emergency'):
+            raise APIError("wrong '%s' value for kind parameter" % kind)
+        family_id = self.get_link(NameID).family_id
+        family = self.get_family_raw(family_id)
+        self.replace_null_values(post_data)
+
+        personList = family[kind + 'PersonList']
+        for i, person in enumerate(personList):
+            if str(person['numPerson']) == person_id:
+                personList[i] = post_data
+                personList[i]['numPerson'] = person_id
+                break
+        else:
+            raise APIError(
+                "no '%s' authorized person on '%s' family" % (person_id, family_id), err_code='not-found'
+            )
+        payload = {
+            'dossierNumber': family_id,
+            'categorie': family['category'],
+            'situation': family['situation'],
+            kind + 'PersonList': [{'personList': personList}],
+        }
+        self.call('Family', 'updateFamily', **payload)
+        return {'data': 'ok'}
+
+    @endpoint(
+        display_category=_('Family'),
+        description="Suppression d'une personne authorisée à récupérer les enfants ou à prévenir en cas d'urgence",
+        name='delete-person',
+        perm='can_access',
+        parameters={
+            'NameID': {'description': _('Publik ID')},
+            'person_id': {'description': 'Numéro de la personne'},
+            'kind': {'description': "'authorized' (par defaut) ou 'emergency'"},
+        },
+        methods=['post'],
+    )
+    def delete_person(self, request, NameID, person_id, kind='authorized'):
+        if kind not in ('authorized', 'emergency'):
+            raise APIError("wrong '%s' value for kind parameter" % kind)
+        family_id = self.get_link(NameID).family_id
+        family = self.get_family_raw(family_id)
+
+        personList = family[kind + 'PersonList']
+        for i, person in enumerate(personList):
+            if str(person['numPerson']) == person_id:
+                del personList[i]
+                break
+        else:
+            raise APIError(
+                "no '%s' authorized person on '%s' family" % (person_id, family_id), err_code='not-found'
+            )
+        payload = {
+            'dossierNumber': family_id,
+            'categorie': family['category'],
+            'situation': family['situation'],
+            kind + 'PersonList': [{'personList': personList}],
+        }
+        self.call('Family', 'updateFamily', **payload)
+        return {'data': 'ok'}
+
 
 class Link(models.Model):
     resource = models.ForeignKey(ToulouseMaelis, on_delete=models.CASCADE)
     name_id = models.CharField(blank=False, max_length=256)
     family_id = models.CharField(blank=False, max_length=128)
     created = models.DateTimeField(auto_now_add=True)
     updated = models.DateTimeField(auto_now=True)
 
diff --git a/passerelle/contrib/toulouse_maelis/schemas.py b/passerelle/contrib/toulouse_maelis/schemas.py
index e41d172d..0518bf39 100644
--- a/passerelle/contrib/toulouse_maelis/schemas.py
+++ b/passerelle/contrib/toulouse_maelis/schemas.py
@@ -259,16 +259,93 @@ RLINFO_SCHEMA = {
         'adresse': ADDRESS_SCHEMA,
         'contact': CONTACT_SCHEMA,
         'profession': PROFESSION_SCHEMA,
         'CAFInfo': CAFINFO_SCHEMA,
     },
 }
 
 
+CONTACTLIGHT_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-04/schema#',
+    'title': 'Contact',
+    'description': "Informations de contact pour les personnes authorisées à récupérer les enfants ou à prévenir en cas d'urgence",
+    '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'}],
+                },
+            },
+        },
+    ],
+}
+
+FAMILYPERSON_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-04/schema#',
+    'title': 'Family',
+    'description': "Informations sur les personnes autorisées à venir chercher les enfants ou à prévenir en cas d'urgence",
+    'type': 'object',
+    'required': ['firstname', 'lastname', 'dateBirth'],
+    '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é',
+            'oneOf': [{'type': 'null'}, {'type': 'string'}],
+        },
+        'dateBirth': {
+            'description': 'Date de naissance',
+            'type': 'string',
+            'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$',
+        },
+        'contact': CONTACTLIGHT_SCHEMA,
+    },
+    'unflatten': True,
+}
+
+AUTHORIZEDPERSON_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-04/schema#',
+    'title': 'Family',
+    'description': "Informations sur les personnes autorisées à venir chercher les enfants ou à prévenir en cas d'urgence",
+    'type': 'object',
+    'properties': {
+        'personList': {
+            'oneOf': [
+                {'type': 'null'},
+                {
+                    'type': 'array',
+                    'items': FAMILYPERSON_SCHEMA,
+                },
+            ],
+        },
+    },
+}
+
 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': ['categorie', 'situation'],
     'properties': {
         'categorie': {
@@ -292,16 +369,34 @@ FAMILY_SCHEMA = {
             'oneOf': [{'type': 'null'}, {'type': 'string'}],
         },
         'nbAES': {
             'description': "Nombre d'AES",
             'oneOf': [{'type': 'null'}, {'type': 'string'}],
         },
         'rl1': RLINFO_SCHEMA,
         'rl2': RLINFO_SCHEMA,
+        'authorizedPersonList': {
+            'oneOf': [
+                {'type': 'null'},
+                {
+                    'type': 'array',
+                    'items': AUTHORIZEDPERSON_SCHEMA,
+                },
+            ],
+        },
+        'emergencyPersonList': {
+            'oneOf': [
+                {'type': 'null'},
+                {
+                    'type': 'array',
+                    'items': AUTHORIZEDPERSON_SCHEMA,
+                },
+            ],
+        },
     },
     'unflatten': True,
 }
 
 """Schemas below describe parameters of Maelis wrapper around updateFamily endpoint"""
 
 UPDATE_COORDINATE_SCHEMA = {
     '$schema': 'http://json-schema.org/draft-04/schema#',
diff --git a/tests/data/toulouse_maelis/Q_create_person.xml b/tests/data/toulouse_maelis/Q_create_person.xml
new file mode 100644
index 00000000..bf96915a
--- /dev/null
+++ b/tests/data/toulouse_maelis/Q_create_person.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap-env:Header>
+    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
+      <wsse:UsernameToken>
+        <wsse:Username>maelis-webservice</wsse:Username>
+        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">maelis-password</wsse:Password>
+      </wsse:UsernameToken>
+    </wsse:Security>
+  </soap-env:Header>
+  <soap-env:Body>
+    <ns0:updateFamily xmlns:ns0="family.ws.maelis.sigec.com">
+      <dossierNumber>1312</dossierNumber>
+      <categorie>BI</categorie>
+      <situation>M</situation>
+      <emergencyPersonList>
+        <personList>
+          <numPerson>614059</numPerson>
+          <firstname>KENY</firstname>
+          <lastname>ARKANA</lastname>
+          <dateBirth>1970-01-01</dateBirth>
+          <quality>MERE</quality>
+          <contact>
+            <phone>0123456789</phone>
+          </contact>
+        </personList>
+        <personList>
+          <civility/>
+          <firstname>Mathias</firstname>
+          <lastname>Cassel</lastname>
+          <dateBirth>1972-01-01</dateBirth>
+          <quality>PERE</quality>
+          <contact>
+            <phone/>
+            <mobile>0623456789</mobile>
+            <mail/>
+          </contact>
+        </personList>
+      </emergencyPersonList>
+    </ns0:updateFamily>
+  </soap-env:Body>
+</soap-env:Envelope>
diff --git a/tests/data/toulouse_maelis/Q_delete_person.xml b/tests/data/toulouse_maelis/Q_delete_person.xml
new file mode 100644
index 00000000..c9bf9ec0
--- /dev/null
+++ b/tests/data/toulouse_maelis/Q_delete_person.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap-env:Header>
+    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
+      <wsse:UsernameToken>
+        <wsse:Username>maelis-webservice</wsse:Username>
+        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">maelis-password</wsse:Password>
+      </wsse:UsernameToken>
+    </wsse:Security>
+  </soap-env:Header>
+  <soap-env:Body>
+    <ns0:updateFamily xmlns:ns0="family.ws.maelis.sigec.com">
+      <dossierNumber>1312</dossierNumber>
+      <categorie>BI</categorie>
+      <situation>M</situation>
+      <emergencyPersonList/>
+    </ns0:updateFamily>
+  </soap-env:Body>
+</soap-env:Envelope>
diff --git a/tests/data/toulouse_maelis/Q_update_person.xml b/tests/data/toulouse_maelis/Q_update_person.xml
new file mode 100644
index 00000000..a8e9c8e1
--- /dev/null
+++ b/tests/data/toulouse_maelis/Q_update_person.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap-env:Header>
+    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
+      <wsse:UsernameToken>
+        <wsse:Username>maelis-webservice</wsse:Username>
+        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">maelis-password</wsse:Password>
+      </wsse:UsernameToken>
+    </wsse:Security>
+  </soap-env:Header>
+  <soap-env:Body>
+    <ns0:updateFamily xmlns:ns0="family.ws.maelis.sigec.com">
+      <dossierNumber>1312</dossierNumber>
+      <categorie>BI</categorie>
+      <situation>M</situation>
+      <emergencyPersonList>
+        <personList>
+          <numPerson>614059</numPerson>
+          <civility/>
+          <firstname>Mathias</firstname>
+          <lastname>Cassel</lastname>
+          <dateBirth>1972-01-01</dateBirth>
+          <quality>PERE</quality>
+          <contact>
+            <phone/>
+            <mobile>0623456789</mobile>
+            <mail/>
+          </contact>
+        </personList>
+      </emergencyPersonList>
+    </ns0:updateFamily>
+  </soap-env:Body>
+</soap-env:Envelope>
diff --git a/tests/data/toulouse_maelis/R_read_family.xml b/tests/data/toulouse_maelis/R_read_family.xml
index faba8d64..84cbdc6e 100644
--- a/tests/data/toulouse_maelis/R_read_family.xml
+++ b/tests/data/toulouse_maelis/R_read_family.xml
@@ -54,16 +54,36 @@
             <isContactMail>false</isContactMail>
             <isContactSms>false</isContactSms>
             <isInvoicePdf>false</isInvoicePdf>
           </contact>
           <CAFInfo>
             <number>51</number>
           </CAFInfo>
         </RL2>
+        <emergencyPersonList>
+          <num>614059</num>
+          <id>S10032140599</id>
+          <firstname>KENY</firstname>
+          <lastname>ARKANA</lastname>
+          <quality>MERE</quality>
+          <contact>
+            <phone>0123456789</phone>
+          </contact>
+        </emergencyPersonList>
+        <authorizedPersonList>
+          <numPerson>614058</numPerson>
+          <firstname>CATHY</firstname>
+          <lastname>PALENNE</lastname>
+          <dateBirth>1975-06-28T00:00:00+01:00</dateBirth>
+          <quality>PERE</quality>
+          <contact>
+            <phone>0123456789</phone>
+          </contact>
+        </authorizedPersonList>
         <childList>
           <num>613880</num>
           <lastname>COSTANZE</lastname>
           <firstname>CASSANDRA</firstname>
           <sexe>F</sexe>
           <birth>
             <dateBirth>2021-06-22T00:00:00+02:00</dateBirth>
           </birth>
diff --git a/tests/test_toulouse_maelis.py b/tests/test_toulouse_maelis.py
index 42bb8bd9..87dc7ed4 100644
--- a/tests/test_toulouse_maelis.py
+++ b/tests/test_toulouse_maelis.py
@@ -445,16 +445,26 @@ def test_read_family(mocked_post, mocked_get, con, app):
         'medicalRecord': None,
         'subscribeSchoolList': [],
         'mother': None,
         'father': None,
         'rl': None,
         'subscribeActivityList': [],
         'paiInfoBean': None,
     }
+    assert resp.json['data']['emergencyPersonList'][0] == {
+        'firstname': 'KENY',
+        'lastname': 'ARKANA',
+        'quality': 'MERE',
+        'civility': None,
+        'contact': {'phone': '0123456789', 'mobile': None, 'mail': None},
+        'numPerson': 614059,
+        'dateBirth': '1970-01-01',
+        'quality_text': 'MERE',
+    }
 
 
 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'
@@ -530,16 +540,58 @@ def test_read_rl_not_found(mocked_post, mocked_get, con, app):
     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"
 
 
+@mock.patch('passerelle.utils.Request.get')
+@mock.patch('passerelle.utils.Request.post')
+def test_read_person(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-person')
+    Link.objects.create(resource=con, family_id='1312', name_id='local')
+
+    resp = app.get(url + '?NameID=local&person_id=614059&kind=emergency')
+    assert resp.json['err'] == 0
+    assert resp.json['data']['firstname'] == 'KENY'
+
+
+def test_read_person_wrong_parameter(con, app):
+    url = get_endpoint('read-person')
+
+    resp = app.get(url + '?NameID=local&person_id=000000&kind=baby-sitter')
+    assert resp.json['err'] == 1
+    assert resp.json['err_desc'] == "wrong 'baby-sitter' value for kind parameter"
+
+
+def test_read_person_not_linked_error(con, app):
+    url = get_endpoint('read-person')
+
+    resp = app.get(url + '?NameID=local&person_id=614059')
+    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_person_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-person')
+    Link.objects.create(resource=con, family_id='1312', name_id='local')
+
+    resp = app.get(url + '?NameID=local&person_id=000000&kind=emergency')
+    assert resp.json['err'] == 'not-found'
+    assert resp.json['err_desc'] == "no '000000' emergency person 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')
@@ -766,8 +818,231 @@ 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'"
+
+
+@mock.patch('passerelle.utils.Request.get')
+@mock.patch('passerelle.utils.Request.post')
+def test_create_person(mocked_post, mocked_get, con, app):
+    mocked_get.return_value = FAMILY_SERVICE_WSDL
+    mocked_post.side_effect = [READ_FAMILY, UPDATE_FAMILY]
+    url = get_endpoint('create-person')
+    params = {
+        'civility': None,
+        'firstname': 'Mathias',
+        'lastname': 'Cassel',
+        'quality': 'PERE',
+        'dateBirth': '1972-01-01',
+        'contact/phone': None,
+        'contact/mobile': '0623456789',
+        'contact/mail': None,
+    }
+
+    Link.objects.create(resource=con, family_id='1312', name_id='local')
+    resp = app.post_json(url + '?NameID=local&kind=emergency', params=params)
+    assert_sent_payload(mocked_post, 'Q_create_person.xml')
+    assert resp.json['err'] == 0
+
+
+def test_create_person_wrong_parameter(con, app):
+    url = get_endpoint('create-person')
+    params = {
+        'civility': None,
+        'firstname': 'Mathias',
+        'lastname': 'Cassel',
+        'quality': 'PERE',
+        'dateBirth': '1972-01-01',
+        'contact/phone': None,
+        'contact/mobile': '0623456789',
+        'contact/mail': None,
+    }
+
+    resp = app.post_json(url + '?NameID=local&kind=baby-sitter', params=params)
+    assert resp.json['err'] == 1
+    assert resp.json['err_desc'] == "wrong 'baby-sitter' value for kind parameter"
+
+
+def test_create_person_not_linked_error(con, app):
+    url = get_endpoint('create-person')
+    params = {
+        'civility': None,
+        'firstname': 'Mathias',
+        'lastname': 'Cassel',
+        'quality': 'PERE',
+        'dateBirth': '1972-01-01',
+        'contact/phone': None,
+        'contact/mobile': '0623456789',
+        'contact/mail': None,
+    }
+
+    resp = app.post_json(url + '?NameID=local&kind=emergency', 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_person(mocked_post, mocked_get, con, app):
+    mocked_get.return_value = FAMILY_SERVICE_WSDL
+    mocked_post.side_effect = [READ_FAMILY, UPDATE_FAMILY]
+    url = get_endpoint('update-person')
+    params = {
+        'civility': None,
+        'firstname': 'Mathias',
+        'lastname': 'Cassel',
+        'quality': 'PERE',
+        'dateBirth': '1972-01-01',
+        'contact/phone': None,
+        'contact/mobile': '0623456789',
+        'contact/mail': None,
+    }
+
+    Link.objects.create(resource=con, family_id='1312', name_id='local')
+    resp = app.post_json(url + '?NameID=local&person_id=614059&kind=emergency', params=params)
+    assert_sent_payload(mocked_post, 'Q_update_person.xml')
+    assert resp.json['err'] == 0
+
+
+def test_update_person_wrong_parameter(con, app):
+    url = get_endpoint('update-person')
+    params = {
+        'civility': None,
+        'firstname': 'Mathias',
+        'lastname': 'Cassel',
+        'quality': 'PERE',
+        'dateBirth': '1972-01-01',
+        'contact/phone': None,
+        'contact/mobile': '0623456789',
+        'contact/mail': None,
+    }
+
+    resp = app.post_json(url + '?NameID=local&person_id=614059&kind=baby-sitter', params=params)
+    assert resp.json['err'] == 1
+    assert resp.json['err_desc'] == "wrong 'baby-sitter' value for kind parameter"
+
+
+def test_update_person_not_linked_error(con, app):
+    url = get_endpoint('update-person')
+    params = {
+        'civility': None,
+        'firstname': 'Mathias',
+        'lastname': 'Cassel',
+        'quality': 'PERE',
+        'dateBirth': '1972-01-01',
+        'contact/phone': None,
+        'contact/mobile': '0623456789',
+        'contact/mail': None,
+    }
+
+    resp = app.post_json(url + '?NameID=local&person_id=614059&kind=emergency', 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_person_not_found(mocked_post, mocked_get, con, app):
+    mocked_get.return_value = FAMILY_SERVICE_WSDL
+    mocked_post.side_effect = [READ_FAMILY, UPDATE_FAMILY]
+    url = get_endpoint('update-person')
+    params = {
+        'civility': None,
+        'firstname': 'Mathias',
+        'lastname': 'Cassel',
+        'quality': 'PERE',
+        'dateBirth': '1972-01-01',
+        'contact/phone': None,
+        'contact/mobile': '0623456789',
+        'contact/mail': None,
+    }
+
+    Link.objects.create(resource=con, family_id='1312', name_id='local')
+    resp = app.post_json(url + '?NameID=local&person_id=000000&kind=emergency', params=params)
+    assert resp.json['err'] == 'not-found'
+    assert resp.json['err_desc'] == "no '000000' authorized person on '1312' family"
+
+
+@mock.patch('passerelle.utils.Request.get')
+@mock.patch('passerelle.utils.Request.post')
+def test_delete_person(mocked_post, mocked_get, con, app):
+    mocked_get.return_value = FAMILY_SERVICE_WSDL
+    mocked_post.side_effect = [READ_FAMILY, UPDATE_FAMILY]
+    url = get_endpoint('delete-person')
+    params = {
+        'civility': None,
+        'firstname': 'Mathias',
+        'lastname': 'Cassel',
+        'quality': 'PERE',
+        'dateBirth': '1972-01-01',
+        'contact/phone': None,
+        'contact/mobile': '0623456789',
+        'contact/mail': None,
+    }
+
+    Link.objects.create(resource=con, family_id='1312', name_id='local')
+    resp = app.post_json(url + '?NameID=local&person_id=614059&kind=emergency', params=params)
+    assert_sent_payload(mocked_post, 'Q_delete_person.xml')
+    assert resp.json['err'] == 0
+
+
+def test_delete_person_wrong_parameter(con, app):
+    url = get_endpoint('delete-person')
+    params = {
+        'civility': None,
+        'firstname': 'Mathias',
+        'lastname': 'Cassel',
+        'quality': 'PERE',
+        'dateBirth': '1972-01-01',
+        'contact/phone': None,
+        'contact/mobile': '0623456789',
+        'contact/mail': None,
+    }
+
+    resp = app.post_json(url + '?NameID=local&person_id=614059&kind=baby-sitter', params=params)
+    assert resp.json['err'] == 1
+    assert resp.json['err_desc'] == "wrong 'baby-sitter' value for kind parameter"
+
+
+def test_delete_person_not_linked_error(con, app):
+    url = get_endpoint('delete-person')
+    params = {
+        'civility': None,
+        'firstname': 'Mathias',
+        'lastname': 'Cassel',
+        'quality': 'PERE',
+        'dateBirth': '1972-01-01',
+        'contact/phone': None,
+        'contact/mobile': '0623456789',
+        'contact/mail': None,
+    }
+
+    resp = app.post_json(url + '?NameID=local&person_id=614059&kind=emergency', 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_delete_person_not_found(mocked_post, mocked_get, con, app):
+    mocked_get.return_value = FAMILY_SERVICE_WSDL
+    mocked_post.side_effect = [READ_FAMILY, UPDATE_FAMILY]
+    url = get_endpoint('delete-person')
+    params = {
+        'civility': None,
+        'firstname': 'Mathias',
+        'lastname': 'Cassel',
+        'quality': 'PERE',
+        'dateBirth': '1972-01-01',
+        'contact/phone': None,
+        'contact/mobile': '0623456789',
+        'contact/mail': None,
+    }
+
+    Link.objects.create(resource=con, family_id='1312', name_id='local')
+    resp = app.post_json(url + '?NameID=local&person_id=000000&kind=emergency', params=params)
+    assert resp.json['err'] == 'not-found'
+    assert resp.json['err_desc'] == "no '000000' authorized person on '1312' family"
-- 
2.30.2

