From c767d92f7fa990664d7ad738d924af51024f5d50 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Tue, 12 Jul 2022 18:54:14 +0200
Subject: [PATCH 1/2] toulouse_maelis: parsifal: add referentials webservices
 (#67325)

---
 passerelle/contrib/toulouse_maelis/models.py  | 115 +++++++++++
 .../toulouse_maelis/R_read_category_list.xml  |  19 ++
 .../toulouse_maelis/R_read_civility_list.xml  |  15 ++
 .../data/toulouse_maelis/R_read_csp_list.xml  | 179 +++++++++++++++++
 .../toulouse_maelis/R_read_quality_list.xml   |  75 +++++++
 .../toulouse_maelis/R_read_situation_list.xml |  43 ++++
 tests/test_toulouse_maelis.py                 | 185 +++++++++++++++++-
 7 files changed, 630 insertions(+), 1 deletion(-)
 create mode 100644 tests/data/toulouse_maelis/R_read_category_list.xml
 create mode 100644 tests/data/toulouse_maelis/R_read_civility_list.xml
 create mode 100644 tests/data/toulouse_maelis/R_read_csp_list.xml
 create mode 100644 tests/data/toulouse_maelis/R_read_quality_list.xml
 create mode 100644 tests/data/toulouse_maelis/R_read_situation_list.xml

diff --git a/passerelle/contrib/toulouse_maelis/models.py b/passerelle/contrib/toulouse_maelis/models.py
index 9a30ea73..0482844e 100644
--- a/passerelle/contrib/toulouse_maelis/models.py
+++ b/passerelle/contrib/toulouse_maelis/models.py
@@ -12,16 +12,17 @@
 # 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 <http://www.gnu.org/licenses/>.
 
 from urllib.parse import urljoin
 
 import zeep
+from django.core.cache import cache
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 from zeep.helpers import serialize_object
 from zeep.wsse.username import UsernameToken
 
 from passerelle.base.models import BaseResource, HTTPResource
 from passerelle.utils.api import endpoint
 from passerelle.utils.jsonresponse import APIError
@@ -63,27 +64,141 @@ class ToulouseMaelis(BaseResource, HTTPResource):
             return method(**kwargs)
         except zeep.exceptions.Fault as e:
             raise APIError(e.message, err_code='%s-%s-%s' % (wsdl_short_name, service, e.code))
 
     def check_status(self):
         assert self.call('Family', 'isWSRunning')
         assert self.call('Activity', 'isWSRunning')
 
+    def get_referential(self, referential_name):
+
+        # local referentials
+        if referential_name == 'Complement':
+            response = [
+                {'id': 'B', 'text': 'bis'},
+                {'id': 'T', 'text': 'ter'},
+                {'id': 'Q', 'text': 'quater'},
+            ]
+            return {'list': response, 'dict': {x['id']: x['text'] for x in response}}
+
+        # remote referentials
+        cache_key = 'maelis-%s-%s' % (self.pk, referential_name)
+        data = cache.get(cache_key)
+        if data is None:
+            response = self.call('Family', 'read' + referential_name + 'List')
+            data = {
+                'list': [{'id': x.code, 'text': x.libelle} for x in response],
+                'dict': {x.code: x.libelle for x in response},
+            }
+            # put in cache for two hours
+            cache.set(cache_key, data, 3600 * 2)
+        return data
+
+    def get_referential_value(self, referential_name, key):
+        try:
+            return self.get_referential(referential_name)['dict'][key]
+        except KeyError:
+            # Maelis DB not properly configurated
+            self.logger.warning("No '%s' key into Maelis '%s' referential", key, referential_name)
+            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):
         response = self.call('Family', 'readFamily', dossierNumber=family_id)
         data = serialize_object(response)
+
+        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])
+
+        # add text from referentials
+        add_text_value('Category', data, ['category'])
+        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'])
         return data
 
+    @endpoint(
+        display_category=_('Family'),
+        description='Liste des catégories',
+        name='read-category-list',
+        perm='can_access',
+    )
+    def read_category_list(self, request):
+        return {'data': self.get_referential('Category')['list']}
+
+    @endpoint(
+        display_category=_('Family'),
+        description='Liste des civilités',
+        name='read-civility-list',
+        perm='can_access',
+    )
+    def read_civility_list(self, request):
+        return {'data': self.get_referential('Civility')['list']}
+
+    @endpoint(
+        display_category=_('Family'),
+        description='Liste des compléments du numéro de voie',
+        name='read-complement-list',
+        perm='can_access',
+    )
+    def read_complement_list(self, request):
+        return {'data': self.get_referential('Complement')['list']}
+
+    @endpoint(
+        display_category=_('Family'),
+        description='liste des catégories socio-professionnelles',
+        name='read-csp-list',
+        perm='can_access',
+    )
+    def read_csp_list(self, request):
+        data = self.get_referential('CSP')['list']
+
+        # remove redundant codes
+        uniq_text = set()
+        uniq_data = []
+        for item in data:
+            item['text'] = item['text'].strip()
+            if item['text'] not in uniq_text:
+                uniq_data.append(item)
+                uniq_text.add(item['text'])
+        return {'data': uniq_data}
+
+    @endpoint(
+        display_category=_('Family'),
+        description='liste des qualités du référenciel',
+        name='read-quality-list',
+        perm='can_access',
+    )
+    def read_quality_list(self, request):
+        return {'data': self.get_referential('Quality')['list']}
+
+    @endpoint(
+        display_category=_('Family'),
+        description='liste des situations',
+        name='read-situation-list',
+        perm='can_access',
+    )
+    def read_situation_list(self, request):
+        return {'data': self.get_referential('Situation')['list']}
+
     @endpoint(
         display_category=_('Family'),
         description=_('Create link between user and family'),
         perm='can_access',
         parameters={'NameID': {'description': _('Publik ID')}},
         post={'request_body': {'schema': {'application/json': schemas.LINK_SCHEMA}}},
     )
     def link(self, request, NameID, post_data):
diff --git a/tests/data/toulouse_maelis/R_read_category_list.xml b/tests/data/toulouse_maelis/R_read_category_list.xml
new file mode 100644
index 00000000..19d2d933
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_read_category_list.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap:Body>
+    <ns2:readCategoryListResponse xmlns:ns2="family.ws.maelis.sigec.com">
+      <itemList>
+        <code>BI</code>
+        <libelle>BIPARENTALE</libelle>
+      </itemList>
+      <itemList>
+        <code>ACCEUI</code>
+        <libelle>FAMILLE D'ACCUEIL</libelle>
+      </itemList>
+      <itemList>
+        <code>MONO</code>
+        <libelle>MONOPARENTALE</libelle>
+      </itemList>
+    </ns2:readCategoryListResponse>
+  </soap:Body>
+</soap:Envelope>
diff --git a/tests/data/toulouse_maelis/R_read_civility_list.xml b/tests/data/toulouse_maelis/R_read_civility_list.xml
new file mode 100644
index 00000000..012c222d
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_read_civility_list.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap:Body>
+    <ns2:readCivilityListResponse xmlns:ns2="family.ws.maelis.sigec.com">
+      <itemList>
+        <code>MME</code>
+        <libelle>Madame</libelle>
+      </itemList>
+      <itemList>
+        <code>M.</code>
+        <libelle>Monsieur</libelle>
+      </itemList>
+    </ns2:readCivilityListResponse>
+  </soap:Body>
+</soap:Envelope>
diff --git a/tests/data/toulouse_maelis/R_read_csp_list.xml b/tests/data/toulouse_maelis/R_read_csp_list.xml
new file mode 100644
index 00000000..e389e734
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_read_csp_list.xml
@@ -0,0 +1,179 @@
+<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap:Body>
+    <ns2:readCSPListResponse xmlns:ns2="family.ws.maelis.sigec.com">
+      <itemList>
+        <code>14</code>
+        <libelle>AGENT DE MAITRISE</libelle>
+      </itemList>
+      <itemList>
+        <code>1</code>
+        <libelle>AGRICULTEUR</libelle>
+      </itemList>
+      <itemList>
+        <code>AGR</code>
+        <libelle>AGRICULTEUR</libelle>
+      </itemList>
+      <itemList>
+        <code>ARTI</code>
+        <libelle>ARTISAN</libelle>
+      </itemList>
+      <itemList>
+        <code>ART</code>
+        <libelle>ARTISAN</libelle>
+      </itemList>
+      <itemList>
+        <code>2</code>
+        <libelle>ARTISAN-COMMERCANT</libelle>
+      </itemList>
+      <itemList>
+        <code>15</code>
+        <libelle>AUTRES</libelle>
+      </itemList>
+      <itemList>
+        <code>CADR</code>
+        <libelle>CADRE</libelle>
+      </itemList>
+      <itemList>
+        <code>4</code>
+        <libelle>CADRE</libelle>
+      </itemList>
+      <itemList>
+        <code>13</code>
+        <libelle>CADRE SUPERIEUR</libelle>
+      </itemList>
+      <itemList>
+        <code>3</code>
+        <libelle>CHEF D'ENTREPRISE</libelle>
+      </itemList>
+      <itemList>
+        <code>CHOM</code>
+        <libelle>CHOMEUR</libelle>
+      </itemList>
+      <itemList>
+        <code>COM</code>
+        <libelle>COMMERCANT</libelle>
+      </itemList>
+      <itemList>
+        <code>COMM</code>
+        <libelle>COMMERCANT</libelle>
+      </itemList>
+      <itemList>
+        <code>7</code>
+        <libelle>COMMERCANT</libelle>
+      </itemList>
+      <itemList>
+        <code>10</code>
+        <libelle>DEMANDEUR D'EMPLOI</libelle>
+      </itemList>
+      <itemList>
+        <code>DIV</code>
+        <libelle>DIVERS</libelle>
+      </itemList>
+      <itemList>
+        <code>EMP</code>
+        <libelle>EMPLOYE</libelle>
+      </itemList>
+      <itemList>
+        <code>5</code>
+        <libelle>EMPLOYE</libelle>
+      </itemList>
+      <itemList>
+        <code>ENS</code>
+        <libelle>ENSEIGNANT</libelle>
+      </itemList>
+      <itemList>
+        <code>EN</code>
+        <libelle>ENSEIGNANT</libelle>
+      </itemList>
+      <itemList>
+        <code>17</code>
+        <libelle>ENSEIGNANT</libelle>
+      </itemList>
+      <itemList>
+        <code>ETU</code>
+        <libelle>ETUDIANT</libelle>
+      </itemList>
+      <itemList>
+        <code>8</code>
+        <libelle>ETUDIANT</libelle>
+      </itemList>
+      <itemList>
+        <code>11</code>
+        <libelle>FONCTIONNAIRE</libelle>
+      </itemList>
+      <itemList>
+        <code>FONC</code>
+        <libelle>FONCTIONNAIRE</libelle>
+      </itemList>
+      <itemList>
+        <code>FONCT</code>
+        <libelle>FONCTIONNAIRE </libelle>
+      </itemList>
+      <itemList>
+        <code>MAIR</code>
+        <libelle>MAIRIE DE NICE</libelle>
+      </itemList>
+      <itemList>
+        <code>OUV</code>
+        <libelle>OUVRIER</libelle>
+      </itemList>
+      <itemList>
+        <code>6</code>
+        <libelle>OUVRIER</libelle>
+      </itemList>
+      <itemList>
+        <code>PERENS</code>
+        <libelle>PERISCO ENSEIGNANT</libelle>
+      </itemList>
+      <itemList>
+        <code>PEREXT</code>
+        <libelle>PERISCO EXTERNE</libelle>
+      </itemList>
+      <itemList>
+        <code>PERMAI</code>
+        <libelle>PERISCO MAIRIE DE NICE</libelle>
+      </itemList>
+      <itemList>
+        <code>PERANI</code>
+        <libelle>PERISCO S.ANIMATION</libelle>
+      </itemList>
+      <itemList>
+        <code>9</code>
+        <libelle>PROFESSION LIBERALE</libelle>
+      </itemList>
+      <itemList>
+        <code>LIB</code>
+        <libelle>PROFESSION LIBERALE</libelle>
+      </itemList>
+      <itemList>
+        <code>12</code>
+        <libelle>RETRAITE</libelle>
+      </itemList>
+      <itemList>
+        <code>RET</code>
+        <libelle>RETRAITE</libelle>
+      </itemList>
+      <itemList>
+        <code>RMI</code>
+        <libelle>REVENU MINIMUM D'INSERTION</libelle>
+      </itemList>
+      <itemList>
+        <code>16</code>
+        <libelle>SANS PROFESSION</libelle>
+      </itemList>
+      <itemList>
+        <code>SANPRO</code>
+        <libelle>SANS PROFESSION</libelle>
+      </itemList>
+      <itemList>
+        <code>SANS</code>
+        <libelle>SANS PROFESSION</libelle>
+      </itemList>
+      <itemList>
+        <code>SEC</code>
+        <libelle>SECRETAIRE</libelle>
+      </itemList>
+    </ns2:readCSPListResponse>
+  </soap:Body>
+</soap:Envelope>
diff --git a/tests/data/toulouse_maelis/R_read_quality_list.xml b/tests/data/toulouse_maelis/R_read_quality_list.xml
new file mode 100644
index 00000000..f86e3147
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_read_quality_list.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap:Body>
+    <ns2:readQualityListResponse xmlns:ns2="family.ws.maelis.sigec.com">
+      <itemList>
+        <code>AU</code>
+        <libelle>AUTRE</libelle>
+      </itemList>
+      <itemList>
+        <code>BP</code>
+        <libelle>BEAU PERE</libelle>
+      </itemList>
+      <itemList>
+        <code>BM</code>
+        <libelle>BELLE MERE</libelle>
+      </itemList>
+      <itemList>
+        <code>CONSO</code>
+        <libelle>CONSOMMATEUR</libelle>
+      </itemList>
+      <itemList>
+        <code>EN</code>
+        <libelle>ENFANT</libelle>
+      </itemList>
+      <itemList>
+        <code>FS</code>
+        <libelle>FRERES ET SOEURS</libelle>
+      </itemList>
+      <itemList>
+        <code>GM</code>
+        <libelle>GRAND MERE MATERNELLE</libelle>
+      </itemList>
+      <itemList>
+        <code>GP</code>
+        <libelle>GRAND PERE MATERNEL</libelle>
+      </itemList>
+      <itemList>
+        <code>GMP</code>
+        <libelle>GRAND-MERE PATERNELLE</libelle>
+      </itemList>
+      <itemList>
+        <code>GPP</code>
+        <libelle>GRAND-PERE PATERNEL</libelle>
+      </itemList>
+      <itemList>
+        <code>MAIRIE</code>
+        <libelle>MAIRIE</libelle>
+      </itemList>
+      <itemList>
+        <code>MERE</code>
+        <libelle>MERE</libelle>
+      </itemList>
+      <itemList>
+        <code>O</code>
+        <libelle>ONCLE</libelle>
+      </itemList>
+      <itemList>
+        <code>OS</code>
+        <libelle>ORGANISME SOCIAL</libelle>
+      </itemList>
+      <itemList>
+        <code>PERE</code>
+        <libelle>PERE</libelle>
+      </itemList>
+      <itemList>
+        <code>T</code>
+        <libelle>TANTE</libelle>
+      </itemList>
+      <itemList>
+        <code>TUTEUR</code>
+        <libelle>TUTEUR</libelle>
+      </itemList>
+    </ns2:readQualityListResponse>
+  </soap:Body>
+</soap:Envelope>
diff --git a/tests/data/toulouse_maelis/R_read_situation_list.xml b/tests/data/toulouse_maelis/R_read_situation_list.xml
new file mode 100644
index 00000000..cb3f7d58
--- /dev/null
+++ b/tests/data/toulouse_maelis/R_read_situation_list.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap:Body>
+    <ns2:readSituationListResponse xmlns:ns2="family.ws.maelis.sigec.com">
+      <itemList>
+        <code>C</code>
+        <libelle>Célibataire</libelle>
+      </itemList>
+      <itemList>
+        <code>D</code>
+        <libelle>Divorcé (e)</libelle>
+      </itemList>
+      <itemList>
+        <code>CS</code>
+        <libelle>EN COURS DE SEPARATION</libelle>
+      </itemList>
+      <itemList>
+        <code>M</code>
+        <libelle>Marié (e)</libelle>
+      </itemList>
+      <itemList>
+        <code>P</code>
+        <libelle>Pacsé (e)</libelle>
+      </itemList>
+      <itemList>
+        <code>S</code>
+        <libelle>Séparé (e)</libelle>
+      </itemList>
+      <itemList>
+        <code>UL</code>
+        <libelle>UNION LIBRE</libelle>
+      </itemList>
+      <itemList>
+        <code>V</code>
+        <libelle>Veuf (ve)</libelle>
+      </itemList>
+      <itemList>
+        <code>VM</code>
+        <libelle>Vivant maritalement</libelle>
+      </itemList>
+    </ns2:readSituationListResponse>
+  </soap:Body>
+</soap:Envelope>
diff --git a/tests/test_toulouse_maelis.py b/tests/test_toulouse_maelis.py
index ed16a1b6..1f95d2d6 100644
--- a/tests/test_toulouse_maelis.py
+++ b/tests/test_toulouse_maelis.py
@@ -9,16 +9,17 @@
 # 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 <http://www.gnu.org/licenses/>.
 
+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
@@ -35,16 +36,21 @@ def get_xml_file(filename):
 
 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
 
 
@@ -155,32 +161,209 @@ def test_unlink(con, app):
     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'},
+    ]
+
+
+@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]
+    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',
-- 
2.30.2

