From a956aa276f3115e66ce9e7a0fde939a57b879f18 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Sun, 11 Sep 2022 17:06:52 +0200
Subject: [PATCH 4/5] toulouse_maelis: add FSL webservice (#69045)

---
 passerelle/contrib/toulouse_maelis/models.py  | 47 ++++++++++
 passerelle/contrib/toulouse_maelis/schemas.py | 16 ++--
 .../toulouse_maelis/Q_update_child_fsl.xml    | 38 +++++++++
 .../toulouse_maelis/R_update_child_fsl.xml    |  5 ++
 .../R_update_child_fsl_soap_error.xml         | 15 ++++
 tests/test_toulouse_maelis.py                 | 85 +++++++++++++++++--
 6 files changed, 191 insertions(+), 15 deletions(-)
 create mode 100644 tests/data/toulouse_maelis/Q_update_child_fsl.xml
 create mode 100644 tests/data/toulouse_maelis/R_update_child_fsl.xml
 create mode 100644 tests/data/toulouse_maelis/R_update_child_fsl_soap_error.xml

diff --git a/passerelle/contrib/toulouse_maelis/models.py b/passerelle/contrib/toulouse_maelis/models.py
index e40f56b2..5c8cf059 100644
--- a/passerelle/contrib/toulouse_maelis/models.py
+++ b/passerelle/contrib/toulouse_maelis/models.py
@@ -153,16 +153,30 @@ class ToulouseMaelis(BaseResource, HTTPResource):
         for child in data['childList']:
             add_text_value('Sex', child, ['sexe'])
             add_text_value('DietCode', child, ['dietcode'])
             add_text_value('PAI', child, ['paiInfoBean', 'code'])
         for kind in ('authorized', 'emergency'):
             for person in data[kind + 'PersonList']:
                 add_text_value('Civility', person, ['civility'])
                 add_text_value('Quality', person, ['quality'])
+
+        # convert O/N string into boolean
+        for child in data['childList']:
+            if child.get('fsl'):
+                for key in (
+                    'allergieAlimentaire',
+                    'allergieRespiratoire',
+                    'allergieAutre',
+                    'allergieMedicament',
+                    'asthme',
+                    'flPAI',
+                    'flImage',
+                ):
+                    child['fsl'][key] = bool(child['fsl'][key] == 'O')
         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:
@@ -619,16 +633,49 @@ class ToulouseMaelis(BaseResource, HTTPResource):
         # use None to empty date passed as an empty string by date filter
         for key in 'dateDeb', 'dateFin':
             if post_data[key] == '':
                 post_data[key] = None
 
         self.call('Family', 'updateChildPAI', personNumber=child_id, **post_data)
         return {'data': 'ok'}
 
+    @endpoint(
+        display_category='Famille',
+        description="Créer ou mettre à jour la fiche sanitaire d'un enfant",
+        name='update-child-fsl',
+        perm='can_access',
+        parameters={
+            'NameID': {'description': 'Publik NameID'},
+            'child_id': {'description': "Numéro de l'enfant"},
+        },
+        post={'request_body': {'schema': {'application/json': schemas.FSL_SCHEMA}}},
+    )
+    def update_child_fsl(self, request, NameID, child_id, post_data):
+        self.get_link(NameID)
+
+        # maelis expect strings O/N
+        for key in (
+            'allergieAlimentaire',
+            'allergieRespiratoire',
+            'allergieAutre',
+            'allergieMedicament',
+            'asthme',
+            'flPAI',
+            'flImage',
+        ):
+            post_data[key] = 'O' if post_data.get(key) else 'N'
+
+        # use None to empty optional date passed as an empty string by date filter
+        if post_data.get('dtcPrap1') == '':
+            post_data['dtcPrap1'] = None
+
+        self.call('Family', 'createOrUpdateFSL', arg0=child_id, arg1=post_data)
+        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 19f595a0..02c0b965 100644
--- a/passerelle/contrib/toulouse_maelis/schemas.py
+++ b/passerelle/contrib/toulouse_maelis/schemas.py
@@ -281,46 +281,46 @@ FSL_SCHEMA = {
                 },
                 'dateFin': {
                     'description': 'Date de fin',
                     'type': 'string',
                     'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$',
                 },
                 'allergieAlimentaire': {
                     'description': 'Allergie alimentaire',
-                    'oneOf': [{'type': 'null'}, {'type': 'string'}],
+                    'oneOf': [{'type': 'null'}] + BOOLEAN_TYPES,
                 },
                 'allergieRespiratoire': {
                     'description': 'Allergie respiratoire',
-                    'oneOf': [{'type': 'null'}, {'type': 'string'}],
+                    'oneOf': [{'type': 'null'}] + BOOLEAN_TYPES,
                 },
                 'allergieAutre': {
                     'description': 'Allergie autre',
-                    'oneOf': [{'type': 'null'}, {'type': 'string'}],
+                    'oneOf': [{'type': 'null'}] + BOOLEAN_TYPES,
                 },
                 'allergieMedicament': {
                     'description': 'Allergie médicament',
-                    'oneOf': [{'type': 'null'}, {'type': 'string'}],
+                    'oneOf': [{'type': 'null'}] + BOOLEAN_TYPES,
                 },
                 'asthme': {
                     'description': 'Asthmatique',
-                    'oneOf': [{'type': 'null'}, {'type': 'string'}],
+                    'oneOf': [{'type': 'null'}] + BOOLEAN_TYPES,
                 },
                 'flPAI': {
                     'description': 'PAI',
-                    'oneOf': [{'type': 'null'}, {'type': 'string'}],
+                    'oneOf': [{'type': 'null'}] + BOOLEAN_TYPES,
                 },
                 'flImage': {
                     'description': 'Autorisation photo',
-                    'oneOf': [{'type': 'null'}, {'type': 'string'}],
+                    'oneOf': [{'type': 'null'}] + BOOLEAN_TYPES,
                 },
                 'dtcPrap1': {
                     'description': 'Date du dernier rappel DT Polio',
                     'type': 'string',
-                    'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$',
+                    'pattern': '^([0-9]{4}-[0-9]{2}-[0-9]{2}){0,1}$',
                 },
                 'obsMed1': {
                     'description': 'Observation médecin 1',
                     'oneOf': [{'type': 'null'}, {'type': 'string'}],
                 },
                 'obsMed2': {
                     'description': 'Observation médecin 2',
                     'oneOf': [{'type': 'null'}, {'type': 'string'}],
diff --git a/tests/data/toulouse_maelis/Q_update_child_fsl.xml b/tests/data/toulouse_maelis/Q_update_child_fsl.xml
new file mode 100644
index 00000000..f47fa565
--- /dev/null
+++ b/tests/data/toulouse_maelis/Q_update_child_fsl.xml
@@ -0,0 +1,38 @@
+<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:createOrUpdateFSL xmlns:ns0="family.ws.maelis.sigec.com">
+      <arg0>613878</arg0>
+      <arg1>
+        <dateDeb>2022-01-01</dateDeb>
+        <dateFin>2022-12-31</dateFin>
+        <allergieAlimentaire>O</allergieAlimentaire>
+        <allergieRespiratoire>O</allergieRespiratoire>
+        <allergieAutre>O</allergieAutre>
+        <allergieMedicament>O</allergieMedicament>
+        <asthme>N</asthme>
+        <flPAI>N</flPAI>
+        <flImage>N</flImage>
+        <dtcPrap1>2022-02-22</dtcPrap1>
+        <obsMed1>some obsMed1 text</obsMed1>
+        <obsMed2>some obsMed2 text</obsMed2>
+        <obsMed3>some obsMed3 text</obsMed3>
+        <obsDir1>some obsDir1 text</obsDir1>
+        <obsDir2>some obsDir2 text</obsDir2>
+        <obsDir3>some obsDir3 text</obsDir3>
+        <obsAssist1>some obsAssist1 text</obsAssist1>
+        <obsAssist2>some obsAssist2 text</obsAssist2>
+        <obsAssist3>some obsAssist3 text</obsAssist3>
+        <cons1Med>some cons1Med text</cons1Med>
+        <cons2Med>some cons2Med text</cons2Med>
+      </arg1>
+    </ns0:createOrUpdateFSL>
+  </soap-env:Body>
+</soap-env:Envelope>
diff --git a/tests/data/toulouse_maelis/R_update_child_fsl.xml b/tests/data/toulouse_maelis/R_update_child_fsl.xml
new file mode 100644
index 00000000..d263173c
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_update_child_fsl.xml
@@ -0,0 +1,5 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap:Body>
+    <ns2:createOrUpdateFSLResponse xmlns:ns2="family.ws.maelis.sigec.com"/>
+  </soap:Body>
+</soap:Envelope>
diff --git a/tests/data/toulouse_maelis/R_update_child_fsl_soap_error.xml b/tests/data/toulouse_maelis/R_update_child_fsl_soap_error.xml
new file mode 100644
index 00000000..95db63d5
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_update_child_fsl_soap_error.xml
@@ -0,0 +1,15 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap:Body>
+    <soap:Fault>
+      <faultcode>soap:Server</faultcode>
+      <faultstring>Une erreur est survenue : java.sql.SQLException: ORA-12899: valeur trop grande pour la colonne "MAELIS"."H_FICHESAN"."FS_OBSMED1" (réelle : 71, maximum : 70)
+</faultstring>
+      <detail>
+        <ns1:MaelisFamilyException xmlns:ns1="family.ws.maelis.sigec.com">
+          <message xmlns:ns2="family.ws.maelis.sigec.com">Une erreur est survenue : java.sql.SQLException: ORA-12899: valeur trop grande pour la colonne "MAELIS"."H_FICHESAN"."FS_OBSMED1" (réelle : 71, maximum : 70)
+</message>
+        </ns1:MaelisFamilyException>
+      </detail>
+    </soap:Fault>
+  </soap:Body>
+</soap:Envelope>
diff --git a/tests/test_toulouse_maelis.py b/tests/test_toulouse_maelis.py
index 7fca4440..fa422ccc 100644
--- a/tests/test_toulouse_maelis.py
+++ b/tests/test_toulouse_maelis.py
@@ -62,16 +62,18 @@ IS_CHILD_EXISTS_FALSE = FakedResponse(
 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)
 UPDATE_DIETCODE = FakedResponse(content=get_xml_file('R_update_child_dietcode.xml'), status_code=200)
 UPDATE_PAI = FakedResponse(content=get_xml_file('R_update_child_pai.xml'), status_code=200)
 UPDATE_PAI_500 = FakedResponse(content=get_xml_file('R_update_child_pai_soap_error.xml'), status_code=500)
+UPDATE_FSL = FakedResponse(content=get_xml_file('R_update_child_fsl.xml'), status_code=200)
+UPDATE_FSL_500 = FakedResponse(content=get_xml_file('R_update_child_fsl_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()
 
 
@@ -610,23 +612,23 @@ def test_read_family(mocked_post, mocked_get, con, app):
             'dateDeb': '2022-01-01T00:00:00+01:00',
             'dateFin': '2022-12-31T00:00:00+01:00',
             'description': 'bla bla PAI',
         },
     }
     assert resp.json['data']['childList'][0]['fsl'] == {
         'dateDeb': '2022-01-01T00:00:00+01:00',
         'dateFin': '2022-12-31T00:00:00+01:00',
-        'allergieAlimentaire': 'O',
-        'allergieRespiratoire': 'O',
-        'allergieAutre': 'O',
-        'allergieMedicament': 'O',
-        'asthme': 'N',
-        'flPAI': 'N',
-        'flImage': 'N',
+        'allergieAlimentaire': True,
+        'allergieRespiratoire': True,
+        'allergieAutre': True,
+        'allergieMedicament': True,
+        'asthme': False,
+        'flPAI': False,
+        'flImage': False,
         'dtcPrap1': '2022-02-22T00:00:00+01:00',
         'obsMed1': 'some obsMed1 text',
         'obsMed2': 'some obsMed2 text',
         'obsMed3': 'some obsMed3 text',
         'obsDir1': 'some obsDir1 text',
         'obsDir2': 'some obsDir2 text',
         'obsDir3': 'some obsDir3 text',
         'obsAssist1': 'some obsAssist1 text',
@@ -1456,8 +1458,77 @@ def test_update_child_pai_soap_error(mocked_post, mocked_get, con, app):
     }
 
     Link.objects.create(resource=con, family_id='1312', name_id='local')
     resp = app.post_json(url + '?NameID=local&child_id=613878', params=params)
     assert resp.json['err'] == 'Family-updateChildPAI-soap:Server'
     assert 'Une erreur est survenue' in resp.json['err_desc']
     assert 'valeur trop grande' in resp.json['err_desc']
     assert 'maximum : 500' in resp.json['err_desc']
+
+
+@mock.patch('passerelle.utils.Request.get')
+@mock.patch('passerelle.utils.Request.post')
+def test_update_child_fsl(mocked_post, mocked_get, con, app):
+    mocked_get.return_value = FAMILY_SERVICE_WSDL
+    mocked_post.return_value = UPDATE_FSL
+    url = get_endpoint('update-child-fsl')
+    params = {
+        'dateDeb': '2022-01-01',
+        'dateFin': '2022-12-31',
+        'allergieAlimentaire': True,
+        'allergieRespiratoire': True,
+        'allergieAutre': True,
+        'allergieMedicament': True,
+        'asthme': False,
+        'flPAI': False,
+        'flImage': False,
+        'dtcPrap1': '2022-02-22',
+        'obsMed1': 'some obsMed1 text',
+        'obsMed2': 'some obsMed2 text',
+        'obsMed3': 'some obsMed3 text',
+        'obsDir1': 'some obsDir1 text',
+        'obsDir2': 'some obsDir2 text',
+        'obsDir3': 'some obsDir3 text',
+        'obsAssist1': 'some obsAssist1 text',
+        'obsAssist2': 'some obsAssist2 text',
+        'obsAssist3': 'some obsAssist3 text',
+        'cons1Med': 'some cons1Med text',
+        'cons2Med': 'some cons2Med text',
+    }
+
+    Link.objects.create(resource=con, family_id='1312', name_id='local')
+    resp = app.post_json(url + '?NameID=local&child_id=613878', params=params)
+    assert_sent_payload(mocked_post, 'Q_update_child_fsl.xml')
+    assert resp.json['err'] == 0
+    assert resp.json['data'] == 'ok'
+
+
+def test_update_child_fsl_not_linked_error(con, app):
+    url = get_endpoint('update-child-fsl')
+    params = {
+        'dateDeb': '2022-01-01',
+        'dateFin': '2022-12-31',
+    }
+
+    resp = app.post_json(url + '?NameID=local&child_id=613878', 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_child_fsl_soap_error(mocked_post, mocked_get, con, app):
+    mocked_get.return_value = FAMILY_SERVICE_WSDL
+    mocked_post.return_value = UPDATE_FSL_500
+    url = get_endpoint('update-child-fsl')
+    params = {
+        'dateDeb': '2022-01-01',
+        'dateFin': '2022-12-31',
+        'obsMed1': 'a' * 71,
+    }
+
+    Link.objects.create(resource=con, family_id='1312', name_id='local')
+    resp = app.post_json(url + '?NameID=local&child_id=613878', params=params)
+    assert resp.json['err'] == 'Family-createOrUpdateFSL-soap:Server'
+    assert 'Une erreur est survenue' in resp.json['err_desc']
+    assert 'valeur trop grande' in resp.json['err_desc']
+    assert 'maximum : 70' in resp.json['err_desc']
-- 
2.30.2

