From 79d5e274b5f60192e01d9a21464106a12f217d30 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Sun, 17 Jul 2022 17:40:53 +0200
Subject: [PATCH] toulouse-maelis: add webservices to manage childs (#67328)

---
 passerelle/contrib/toulouse_maelis/models.py  | 33 ++++++++-
 passerelle/contrib/toulouse_maelis/schemas.py | 73 ++++++++++++++++++-
 .../toulouse_maelis/R_is_child_exists.xml     |  8 ++
 tests/test_toulouse_maelis.py                 | 62 ++++++++++++++++
 4 files changed, 174 insertions(+), 2 deletions(-)
 create mode 100644 tests/data/toulouse_maelis/R_is_child_exists.xml

diff --git a/passerelle/contrib/toulouse_maelis/models.py b/passerelle/contrib/toulouse_maelis/models.py
index be8d6afe..b36d55cb 100644
--- a/passerelle/contrib/toulouse_maelis/models.py
+++ b/passerelle/contrib/toulouse_maelis/models.py
@@ -320,27 +320,58 @@ class ToulouseMaelis(BaseResource, HTTPResource):
             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="Informations sur un enfant",
+        perm='can_access',
+        name='read-child',
+        parameters={
+            'NameID': {'description': _('Publik ID')},
+            'child_id': {'description': "Numéro de l'enfant"},
+        },
+    )
+    def read_child(self, request, NameID, child_id):
+        family_id = self.get_link(NameID).family_id
+        data = self.get_family(family_id)
+        for child in data['childList']:
+            if child['num'] == child_id:
+                break
+        else:
+            raise APIError("no '%s' child on '%s' family" % (child_id, family_id), err_code='not-found')
+        return {'data': child}
+
     @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}}},
+        post={'request_body': {'schema': {'application/json': schemas.ISEXISTS_SCHEMA}}},
     )
     def is_rl_exists(self, request, post_data):
         response = self.call('Family', 'isRLExists', **post_data)
         return {'data': response}
 
+    @endpoint(
+        display_category=_('Family'),
+        description="Vérifier qu'un responsable légal existe en base",
+        perm='can_access',
+        name='is-child-exists',
+        post={'request_body': {'schema': {'application/json': schemas.ISEXISTS_SCHEMA}}},
+    )
+    def is_child_exists(self, request, post_data):
+        response = self.call('Family', 'isChildExists', **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.FAMILY_SCHEMA}}},
     )
diff --git a/passerelle/contrib/toulouse_maelis/schemas.py b/passerelle/contrib/toulouse_maelis/schemas.py
index 0518bf39..83f6e238 100644
--- a/passerelle/contrib/toulouse_maelis/schemas.py
+++ b/passerelle/contrib/toulouse_maelis/schemas.py
@@ -45,17 +45,17 @@ LINK_SCHEMA = {
         'dateBirth': {
             'description': 'Date de naissance du RL1',
             'type': 'string',
             'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$',
         },
     },
 }
 
-ISRLEXISTS_SCHEMA = {
+ISEXISTS_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',
@@ -258,16 +258,78 @@ RLINFO_SCHEMA = {
         },
         'adresse': ADDRESS_SCHEMA,
         'contact': CONTACT_SCHEMA,
         'profession': PROFESSION_SCHEMA,
         'CAFInfo': CAFINFO_SCHEMA,
     },
 }
 
+CHILDBIRTH_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-04/schema#',
+    'title': 'Child birth',
+    'description': "Informations sur la naissance d'un enfant",
+    'type': 'object',
+    'required': ['dateBirth'],
+    'properties': {
+        'dateBirth': {
+            'description': 'Date de naissance',
+            'type': 'string',
+            'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$',
+        },
+        'place': {
+            'description': 'Lieu de naissance',
+            'oneOf': [{'type': 'null'}, {'type': 'string'}],
+        },
+    },
+}
+
+CHILD_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-04/schema#',
+    'title': 'Child',
+    'description': "Informations sur la création d'un enfant",
+    'type': 'object',
+    'required': ['sexe', 'firstname', 'lastname'],
+    'oneOf': [
+        {'required': ['dateBirth']},  # createFamily
+        {'required': ['birth']},  # updateFamily
+    ],
+    'properties': {
+        'firstname': {
+            'description': 'Prénom',
+            'type': 'string',
+        },
+        'lastname': {
+            'description': 'Nom',
+            'type': 'string',
+        },
+        'sexe': {
+            'description': 'Sexe',
+            'type': 'string',
+        },
+        'dateBirth': {
+            'description': 'Date de naissance',
+            'type': 'string',
+            'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$',
+        },
+        'birth': CHILDBIRTH_SCHEMA,
+        'dietcode': {
+            'description': 'Code de régime alimentaire',
+            'oneOf': [{'type': 'null'}, {'type': 'string'}],
+        },
+        'bPhoto': {
+            'description': 'Autorisation photo',
+            'oneOf': BOOLEAN_TYPES,
+        },
+        'bLeaveAlone': {
+            'description': 'Autorisation à partir seul',
+            'oneOf': BOOLEAN_TYPES,
+        },
+    },
+}
 
 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'},
         {
@@ -387,16 +449,25 @@ FAMILY_SCHEMA = {
             'oneOf': [
                 {'type': 'null'},
                 {
                     'type': 'array',
                     'items': AUTHORIZEDPERSON_SCHEMA,
                 },
             ],
         },
+        'childList': {
+            'oneOf': [
+                {'type': 'null'},
+                {
+                    'type': 'array',
+                    'items': CHILD_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/R_is_child_exists.xml b/tests/data/toulouse_maelis/R_is_child_exists.xml
new file mode 100644
index 00000000..2054c44c
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_is_child_exists.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap:Body>
+    <ns2:isChildExistsResponse xmlns:ns2="family.ws.maelis.sigec.com">
+      <result>%s</result>
+    </ns2:isChildExistsResponse>
+  </soap:Body>
+</soap:Envelope>
diff --git a/tests/test_toulouse_maelis.py b/tests/test_toulouse_maelis.py
index 87dc7ed4..5072e7ec 100644
--- a/tests/test_toulouse_maelis.py
+++ b/tests/test_toulouse_maelis.py
@@ -44,16 +44,20 @@ ISWSRUNNING_FALSE = FakedResponse(content=get_xml_file('R_is_ws_running.xml') %
 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)
 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)
+IS_CHILD_EXISTS_TRUE = FakedResponse(content=get_xml_file('R_is_child_exists.xml') % b'true', status_code=200)
+IS_CHILD_EXISTS_FALSE = FakedResponse(
+    content=get_xml_file('R_is_child_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):
@@ -582,16 +586,50 @@ def test_read_person_not_found(mocked_post, mocked_get, con, app):
     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"
 
 
+@mock.patch('passerelle.utils.Request.get')
+@mock.patch('passerelle.utils.Request.post')
+def test_read_child(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-child')
+    Link.objects.create(resource=con, family_id='1312', name_id='local')
+
+    resp = app.get(url + '?NameID=local&child_id=613880')
+    assert resp.json['err'] == 0
+    assert resp.json['data']['firstname'] == 'CASSANDRA'
+
+
+def test_read_child_not_linked_error(con, app):
+    url = get_endpoint('read-child')
+
+    resp = app.get(url + '?NameID=local&child_id=613880')
+    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_child_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-child')
+    Link.objects.create(resource=con, family_id='1312', name_id='local')
+
+    resp = app.get(url + '?NameID=local&child_id=000000')
+    assert resp.json['err'] == 'not-found'
+    assert resp.json['err_desc'] == "no '000000' child 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')
@@ -619,16 +657,40 @@ def test_is_rl_exists_schema_error(con, app):
         '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']
 
 
+@pytest.mark.parametrize(
+    'post_response, result',
+    [
+        (IS_CHILD_EXISTS_TRUE, True),
+        (IS_CHILD_EXISTS_FALSE, False),
+    ],
+)
+@mock.patch('passerelle.utils.Request.get')
+@mock.patch('passerelle.utils.Request.post')
+def test_is_child_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-child-exists')
+
+    params = {
+        'firstname': 'Cassandra',
+        'lastname': 'Costanze',
+        'datebirth': '2021-06-22',
+    }
+    resp = app.post_json(url, params=params)
+    assert resp.json['err'] == 0
+    assert resp.json['data'] == result
+
+
 @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',
-- 
2.30.2

