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

---
 passerelle/contrib/toulouse_maelis/models.py  | 23 +++++++
 passerelle/contrib/toulouse_maelis/schemas.py |  7 +-
 .../toulouse_maelis/Q_update_child_pai.xml    | 18 +++++
 .../toulouse_maelis/R_update_child_pai.xml    |  5 ++
 .../R_update_child_pai_soap_error.xml         | 15 +++++
 tests/test_toulouse_maelis.py                 | 66 +++++++++++++++++++
 6 files changed, 131 insertions(+), 3 deletions(-)
 create mode 100644 tests/data/toulouse_maelis/Q_update_child_pai.xml
 create mode 100644 tests/data/toulouse_maelis/R_update_child_pai.xml
 create mode 100644 tests/data/toulouse_maelis/R_update_child_pai_soap_error.xml

diff --git a/passerelle/contrib/toulouse_maelis/models.py b/passerelle/contrib/toulouse_maelis/models.py
index 970f1210..e40f56b2 100644
--- a/passerelle/contrib/toulouse_maelis/models.py
+++ b/passerelle/contrib/toulouse_maelis/models.py
@@ -148,16 +148,17 @@ class ToulouseMaelis(BaseResource, HTTPResource):
         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'])
             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'])
         return data
 
     def replace_null_values(self, dico):
         '''send null fields as empty SOAP tag to tell maelis to empty the value'''
@@ -596,16 +597,38 @@ class ToulouseMaelis(BaseResource, HTTPResource):
         methods=['post'],
     )
     def update_child_dietcode(self, request, NameID, child_id, dietcode):
         self.get_link(NameID)
 
         self.call('Family', 'createOrUpdateChildDiet', personNumber=child_id, code=dietcode)
         return {'data': 'ok'}
 
+    @endpoint(
+        display_category='Famille',
+        description="Créer ou mettre à jour les informations relatives au PAI d'un enfant",
+        name='update-child-pai',
+        perm='can_access',
+        parameters={
+            'NameID': {'description': 'Publik NameID'},
+            'child_id': {'description': "Numéro de l'enfant"},
+        },
+        post={'request_body': {'schema': {'application/json': schemas.PAIINFO_SCHEMA}}},
+    )
+    def update_child_pai(self, request, NameID, child_id, post_data):
+        self.get_link(NameID)
+
+        # 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'}
+
 
 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 475dd5f7..19f595a0 100644
--- a/passerelle/contrib/toulouse_maelis/schemas.py
+++ b/passerelle/contrib/toulouse_maelis/schemas.py
@@ -454,30 +454,31 @@ MEDICALRECORD_SCHEMA = {
 PAIINFO_SCHEMA = {
     '$schema': 'http://json-schema.org/draft-04/schema#',
     'title': 'PAI Info',
     'description': "Informations médicales",
     'oneOf': [
         {'type': 'null'},
         {
             'type': 'object',
+            'required': ['code'],
             'properties': {
                 'code': {
                     'description': 'Code',
-                    'oneOf': [{'type': 'null'}, {'type': 'string'}],
+                    'type': 'string',
                 },
                 'dateDeb': {
                     'description': 'Date de début',
                     'type': 'string',
-                    'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$',
+                    'pattern': '^([0-9]{4}-[0-9]{2}-[0-9]{2}){0,1}$',
                 },
                 'dateFin': {
                     'description': 'Date de fin',
                     'type': 'string',
-                    'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$',
+                    'pattern': '^([0-9]{4}-[0-9]{2}-[0-9]{2}){0,1}$',
                 },
                 'description': {
                     'description': 'Texte libre de description (max 500 caractères)',
                     'oneOf': [{'type': 'null'}, {'type': 'string'}],
                 },
             },
         },
     ],
diff --git a/tests/data/toulouse_maelis/Q_update_child_pai.xml b/tests/data/toulouse_maelis/Q_update_child_pai.xml
new file mode 100644
index 00000000..e0459c5c
--- /dev/null
+++ b/tests/data/toulouse_maelis/Q_update_child_pai.xml
@@ -0,0 +1,18 @@
+<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:updateChildPAI xmlns:ns0="family.ws.maelis.sigec.com">
+      <personNumber>613878</personNumber>
+      <code>PAIALI</code>
+      <dateDeb>2022-01-01</dateDeb>
+      <description>some text</description>
+    </ns0:updateChildPAI>
+  </soap-env:Body>
+</soap-env:Envelope>
diff --git a/tests/data/toulouse_maelis/R_update_child_pai.xml b/tests/data/toulouse_maelis/R_update_child_pai.xml
new file mode 100644
index 00000000..168482af
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_update_child_pai.xml
@@ -0,0 +1,5 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap:Body>
+    <ns2:updateChildPAIResponse xmlns:ns2="family.ws.maelis.sigec.com"/>
+  </soap:Body>
+</soap:Envelope>
diff --git a/tests/data/toulouse_maelis/R_update_child_pai_soap_error.xml b/tests/data/toulouse_maelis/R_update_child_pai_soap_error.xml
new file mode 100644
index 00000000..67f36e6f
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_update_child_pai_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_PERS"."ER_DESCPAI" (réelle : 800, maximum : 500)
+</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_PERS"."ER_DESCPAI" (réelle : 800, maximum : 500)
+</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 51cc256a..7fca4440 100644
--- a/tests/test_toulouse_maelis.py
+++ b/tests/test_toulouse_maelis.py
@@ -60,16 +60,18 @@ 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)
 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)
 
 
 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()
 
 
@@ -519,16 +521,17 @@ def test_read_family(mocked_post, mocked_get, con, app):
     mocked_post.side_effect = [
         READ_FAMILY,
         READ_CATEGORIES,
         READ_SITUATIONS,
         READ_CIVILITIES,
         READ_QUALITIES,
         READ_CSP,
         READ_DIETCODE,
+        READ_PAI,
     ]
     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
     data = resp.json['data']
     del data['RL1']
@@ -598,16 +601,17 @@ def test_read_family(mocked_post, mocked_get, con, app):
         'indicatorList': [],
         'subscribeSchoolList': [],
         'mother': {'num': 613963, 'civility': 'MME', 'firstname': 'JANE', 'lastname': 'DOE'},
         'father': {'num': 613878, 'civility': 'M.', 'firstname': 'JHON', 'lastname': 'DOE'},
         'rl': None,
         'subscribeActivityList': [],
         'paiInfoBean': {
             'code': 'PAIALI',
+            'code_text': 'ALIMENTAIRE',
             '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',
@@ -683,16 +687,17 @@ def test_read_rl1(mocked_post, mocked_get, con, app):
     mocked_post.side_effect = [
         READ_FAMILY,
         READ_CATEGORIES,
         READ_SITUATIONS,
         READ_CIVILITIES,
         READ_QUALITIES,
         READ_CSP,
         READ_DIETCODE,
+        READ_PAI,
     ]
     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'] == 'JHON'
 
@@ -704,16 +709,17 @@ def test_read_rl2(mocked_post, mocked_get, con, app):
     mocked_post.side_effect = [
         READ_FAMILY,
         READ_CATEGORIES,
         READ_SITUATIONS,
         READ_CIVILITIES,
         READ_QUALITIES,
         READ_CSP,
         READ_DIETCODE,
+        READ_PAI,
     ]
     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',
@@ -768,16 +774,17 @@ def test_read_rl_not_found(mocked_post, mocked_get, con, app):
     mocked_post.side_effect = [
         READ_FAMILY,
         READ_CATEGORIES,
         READ_SITUATIONS,
         READ_CIVILITIES,
         READ_QUALITIES,
         READ_CSP,
         READ_DIETCODE,
+        READ_PAI,
     ]
     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"
 
@@ -789,16 +796,17 @@ def test_read_person(mocked_post, mocked_get, con, app):
     mocked_post.side_effect = [
         READ_FAMILY,
         READ_CATEGORIES,
         READ_SITUATIONS,
         READ_CIVILITIES,
         READ_QUALITIES,
         READ_CSP,
         READ_DIETCODE,
+        READ_PAI,
     ]
     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'
 
@@ -826,16 +834,17 @@ def test_read_person_not_found(mocked_post, mocked_get, con, app):
     mocked_post.side_effect = [
         READ_FAMILY,
         READ_CATEGORIES,
         READ_SITUATIONS,
         READ_CIVILITIES,
         READ_QUALITIES,
         READ_CSP,
         READ_DIETCODE,
+        READ_PAI,
     ]
     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"
 
@@ -847,16 +856,17 @@ def test_read_child(mocked_post, mocked_get, con, app):
     mocked_post.side_effect = [
         READ_FAMILY,
         READ_CATEGORIES,
         READ_SITUATIONS,
         READ_CIVILITIES,
         READ_QUALITIES,
         READ_CSP,
         READ_DIETCODE,
+        READ_PAI,
     ]
     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'] == 'JANNIS'
 
@@ -876,16 +886,17 @@ def test_read_child_not_found(mocked_post, mocked_get, con, app):
     mocked_post.side_effect = [
         READ_FAMILY,
         READ_CATEGORIES,
         READ_SITUATIONS,
         READ_CIVILITIES,
         READ_QUALITIES,
         READ_CSP,
         READ_DIETCODE,
+        READ_PAI,
     ]
     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"
 
@@ -1390,8 +1401,63 @@ def test_update_child_dietcode(mocked_post, mocked_get, con, app):
 
 
 def test_update_child_dietcode_not_linked_error(con, app):
     url = get_endpoint('update-child-dietcode')
 
     resp = app.post_json(url + '?NameID=local&child_id=613878&dietcode=RVS')
     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_pai(mocked_post, mocked_get, con, app):
+    mocked_get.return_value = FAMILY_SERVICE_WSDL
+    mocked_post.return_value = UPDATE_PAI
+    url = get_endpoint('update-child-pai')
+    params = {
+        'code': 'PAIALI',
+        'dateDeb': '2022-01-01',
+        'dateFin': '',
+        'description': 'some 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_pai.xml')
+    assert resp.json['err'] == 0
+    assert resp.json['data'] == 'ok'
+
+
+def test_update_child_pai_not_linked_error(con, app):
+    url = get_endpoint('update-child-pai')
+    params = {
+        'code': 'PAIALI',
+        'dateDeb': '2022-01-01',
+        'dateFin': '',
+        'description': 'some text',
+    }
+
+    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_pai_soap_error(mocked_post, mocked_get, con, app):
+    mocked_get.return_value = FAMILY_SERVICE_WSDL
+    mocked_post.return_value = UPDATE_PAI_500
+    url = get_endpoint('update-child-pai')
+    params = {
+        'code': 'PAIALI',
+        'dateDeb': '2022-01-01',
+        'dateFin': '',
+        'description': 'a' * 501,
+    }
+
+    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']
-- 
2.30.2

