From dba65718f4f0e0592f1da966e31bb6b6a2e2e9fb Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Fri, 4 Nov 2022 14:39:50 +0100
Subject: [PATCH 5/5] toulouse-maelis: add limit parameter to referential
 endpoints (#70982)

---
 passerelle/contrib/toulouse_maelis/models.py | 87 +++++++++++++-------
 tests/test_toulouse_maelis.py                | 26 ++++++
 2 files changed, 83 insertions(+), 30 deletions(-)

diff --git a/passerelle/contrib/toulouse_maelis/models.py b/passerelle/contrib/toulouse_maelis/models.py
index 50bfd120..9c23d90b 100644
--- a/passerelle/contrib/toulouse_maelis/models.py
+++ b/passerelle/contrib/toulouse_maelis/models.py
@@ -125,30 +125,46 @@ class ToulouseMaelis(BaseResource, HTTPResource):
         try:
             return self.get_referential(referential_name)['dict'][key]
         except KeyError:
             if log_warning:
                 # Maelis DB not properly configurated
                 self.logger.warning("No '%s' key into Maelis '%s' referential", key, referential_name)
             return None
 
-    def get_referential_list(self, referential_name, id=None, q=None):
+    def get_referential_list(self, referential_name, id=None, q=None, limit=None):
         data = []
+        max_items = 0
+
+        # full referential is returned if id, q and limit are all not specified
+        if limit or (id is not None or q is not None):
+            try:
+                max_items = min(int(limit), 20)
+            except (TypeError, ValueError):
+                max_items = 10
 
         if id:
             text = self.get_referential_value(referential_name, id)
             if text:
                 data.append({'id': id, 'text': text})
         elif q is not None:
-            key = to_ascii(q.strip()).lower()
-            if key:
-                items = self.get_referential(referential_name)['search'].items()
-                data = [v for k, v in items if key in k]
+            search_key = to_ascii(q.strip()).lower()
+            if search_key:
+                nb_found = 0
+                for key, value in self.get_referential(referential_name)['search'].items():
+                    if search_key in key:
+                        data.append(value)
+                        nb_found += 1
+                        if nb_found >= max_items:
+                            break
         else:
             data = self.get_referential(referential_name)['list']
+
+        if max_items:
+            return data[:max_items]
         return data
 
     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')
 
@@ -395,164 +411,175 @@ class ToulouseMaelis(BaseResource, HTTPResource):
     @endpoint(
         display_category='Famille',
         description='Liste des catégories',
         name='read-category-list',
         perm='can_access',
         parameters={
             'id': {'description': 'Identifiant de l’enregistrement'},
             'q': {'description': 'Recherche en texte intégral'},
+            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
         },
     )
-    def read_category_list(self, request, id=None, q=None):
-        return {'data': self.get_referential_list('Category', id, q)}
+    def read_category_list(self, request, id=None, q=None, limit=None):
+        return {'data': self.get_referential_list('Category', id, q, limit)}
 
     @endpoint(
         display_category='Famille',
         description='Liste des civilités',
         name='read-civility-list',
         perm='can_access',
         parameters={
             'id': {'description': 'Identifiant de l’enregistrement'},
             'q': {'description': 'Recherche en texte intégral'},
+            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
         },
     )
-    def read_civility_list(self, request, id=None, q=None):
-        return {'data': self.get_referential_list('Civility', id, q)}
+    def read_civility_list(self, request, id=None, q=None, limit=None):
+        return {'data': self.get_referential_list('Civility', id, q, limit)}
 
     @endpoint(
         display_category='Famille',
         description='Liste des compléments du numéro de voie',
         name='read-complement-list',
         perm='can_access',
         parameters={
             'id': {'description': 'Identifiant de l’enregistrement'},
             'q': {'description': 'Recherche en texte intégral'},
+            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
         },
     )
-    def read_complement_list(self, request, id=None, q=None):
-        return {'data': self.get_referential_list('Complement', id, q)}
+    def read_complement_list(self, request, id=None, q=None, limit=None):
+        return {'data': self.get_referential_list('Complement', id, q, limit)}
 
     @endpoint(
         display_category='Famille',
         description='liste des catégories socio-professionnelles',
         name='read-csp-list',
         perm='can_access',
         parameters={
             'id': {'description': 'Identifiant de l’enregistrement'},
             'q': {'description': 'Recherche en texte intégral'},
-            'limit': {'description': 'Nombre maximal d’éléments retournés'},
+            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
         },
     )
-    def read_csp_list(self, request, id=None, q=None):
-        return {'data': self.get_referential_list('CSP', id, q)}
+    def read_csp_list(self, request, id=None, q=None, limit=None):
+        return {'data': self.get_referential_list('CSP', id, q, limit)}
 
     @endpoint(
         display_category='Famille',
         description='Liste des régimes alimentaires',
         name='read-dietcode-list',
         perm='can_access',
         parameters={
             'id': {'description': 'Identifiant de l’enregistrement'},
             'q': {'description': 'Recherche en texte intégral'},
+            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
         },
     )
-    def read_dietcode_list(self, request, id=None, q=None):
-        return {'data': self.get_referential_list('DietCode', id, q)}
+    def read_dietcode_list(self, request, id=None, q=None, limit=None):
+        return {'data': self.get_referential_list('DietCode', id, q, limit)}
 
     @endpoint(
         display_category='Famille',
         description='Liste des organismes (CAF)',
         name='read-organ-list',
         perm='can_access',
         parameters={
             'id': {'description': 'Identifiant de l’enregistrement'},
             'q': {'description': 'Recherche en texte intégral'},
+            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
         },
     )
-    def read_organ_list(self, request, id=None, q=None):
-        return {'data': self.get_referential_list('Organ', id, q)}
+    def read_organ_list(self, request, id=None, q=None, limit=None):
+        return {'data': self.get_referential_list('Organ', id, q, limit)}
 
     @endpoint(
         display_category='Famille',
         description="Liste des projet d'accueil individualisés",
         name='read-pai-list',
         perm='can_access',
         parameters={
             'id': {'description': 'Identifiant de l’enregistrement'},
             'q': {'description': 'Recherche en texte intégral'},
+            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
         },
     )
-    def read_pai_list(self, request, id=None, q=None):
-        return {'data': self.get_referential_list('PAI', id, q)}
+    def read_pai_list(self, request, id=None, q=None, limit=None):
+        return {'data': self.get_referential_list('PAI', id, q, limit)}
 
     @endpoint(
         display_category='Famille',
         description='liste des qualités du référenciel',
         name='read-quality-list',
         perm='can_access',
         parameters={
             'id': {'description': 'Identifiant de l’enregistrement'},
             'q': {'description': 'Recherche en texte intégral'},
+            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
         },
     )
-    def read_quality_list(self, request, id=None, q=None):
-        return {'data': self.get_referential_list('Quality', id, q)}
+    def read_quality_list(self, request, id=None, q=None, limit=None):
+        return {'data': self.get_referential_list('Quality', id, q, limit)}
 
     @endpoint(
         display_category='Famille',
         description='Liste des quotients',
         name='read-quotient-list',
         perm='can_access',
         parameters={
             'id': {'description': 'Identifiant de l’enregistrement'},
             'q': {'description': 'Recherche en texte intégral'},
+            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
         },
     )
-    def read_quotient_list(self, request, id=None, q=None):
-        return {'data': self.get_referential_list('Quotient', id, q)}
+    def read_quotient_list(self, request, id=None, q=None, limit=None):
+        return {'data': self.get_referential_list('Quotient', id, q, limit)}
 
     @endpoint(
         display_category='Famille',
         description='Liste des sexes',
         name='read-sex-list',
         perm='can_access',
         parameters={
             'id': {'description': 'Identifiant de l’enregistrement'},
             'q': {'description': 'Recherche en texte intégral'},
+            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
         },
     )
-    def read_sex_list(self, request, id=None, q=None):
-        return {'data': self.get_referential_list('Sex', id, q)}
+    def read_sex_list(self, request, id=None, q=None, limit=None):
+        return {'data': self.get_referential_list('Sex', id, q, limit)}
 
     @endpoint(
         display_category='Famille',
         description='liste des situations',
         name='read-situation-list',
         perm='can_access',
         parameters={
             'id': {'description': 'Identifiant de l’enregistrement'},
             'q': {'description': 'Recherche en texte intégral'},
+            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
         },
     )
-    def read_situation_list(self, request, id=None, q=None):
-        return {'data': self.get_referential_list('Situation', id, q)}
+    def read_situation_list(self, request, id=None, q=None, limit=None):
+        return {'data': self.get_referential_list('Situation', id, q, limit)}
 
     @endpoint(
         display_category='Famille',
         description='Liste des vaccins',
         name='read-vaccin-list',
         perm='can_access',
         parameters={
             'id': {'description': 'Identifiant de l’enregistrement'},
             'q': {'description': 'Recherche en texte intégral'},
+            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
         },
     )
-    def read_vaccin_list(self, request, id=None, q=None):
-        return {'data': self.get_referential_list('Vaccin', id, q)}
+    def read_vaccin_list(self, request, id=None, q=None, limit=None):
+        return {'data': self.get_referential_list('Vaccin', id, q, limit)}
 
     @endpoint(
         display_category='Famille',
         description='Lier un compte usager à une famille',
         perm='can_access',
         parameters={'NameID': {'description': 'Publik NameID'}},
         post={'request_body': {'schema': {'application/json': schemas.LINK_SCHEMA}}},
     )
diff --git a/tests/test_toulouse_maelis.py b/tests/test_toulouse_maelis.py
index c7950761..927ad374 100644
--- a/tests/test_toulouse_maelis.py
+++ b/tests/test_toulouse_maelis.py
@@ -419,16 +419,35 @@ def test_read_organ_list(mocked_post, mocked_get, con, app):
     assert resp.json['data'][:5] == [
         {'id': 'A10000979374', 'text': 'ALC L OLIVIER'},
         {'id': 'A10000979372', 'text': 'ALVA 06'},
         {'id': 'A10000980566', 'text': 'ANTIBES'},
         {'id': 'A10000980388', 'text': 'APAJH'},
         {'id': 'A10016401771', 'text': 'ASCROS'},
     ]
 
+    resp = app.get(url + '?limit=5')
+    assert resp.json['err'] == 0
+    assert len(resp.json['data']) == 5
+    assert resp.json['data'] == [
+        {'id': 'A10000979374', 'text': 'ALC L OLIVIER'},
+        {'id': 'A10000979372', 'text': 'ALVA 06'},
+        {'id': 'A10000980566', 'text': 'ANTIBES'},
+        {'id': 'A10000980388', 'text': 'APAJH'},
+        {'id': 'A10016401771', 'text': 'ASCROS'},
+    ]
+
+    resp = app.get(url + '?limit=90')
+    assert resp.json['err'] == 0
+    assert len(resp.json['data']) == 20
+
+    resp = app.get(url + '?limit=plop)')
+    assert resp.json['err'] == 0
+    assert len(resp.json['data']) == 10
+
 
 @mock.patch('passerelle.utils.Request.get')
 @mock.patch('passerelle.utils.Request.post')
 def test_read_pai_list(mocked_post, mocked_get, con, app):
     mocked_get.return_value = FAMILY_SERVICE_WSDL
     mocked_post.return_value = READ_PAI
     url = get_endpoint('read-pai-list')
 
@@ -532,16 +551,23 @@ def test_read_situation_list(mocked_post, mocked_get, con, app):
     resp = app.get(url + '?q= sÉ ')
     assert resp.json['err'] == 0
     assert resp.json['data'] == [
         {'id': 'CS', 'text': 'EN COURS DE SEPARATION'},
         {'id': 'P', 'text': 'Pacsé (e)'},
         {'id': 'S', 'text': 'Séparé (e)'},
     ]
 
+    resp = app.get(url + '?q= sÉ &limit=2')
+    assert resp.json['err'] == 0
+    assert resp.json['data'] == [
+        {'id': 'CS', 'text': 'EN COURS DE SEPARATION'},
+        {'id': 'P', 'text': 'Pacsé (e)'},
+    ]
+
     resp = app.get(url + '?q= ')
     assert resp.json['err'] == 0
     assert resp.json['data'] == []
 
 
 @mock.patch('passerelle.utils.Request.get')
 @mock.patch('passerelle.utils.Request.post')
 def test_read_vaccin_list(mocked_post, mocked_get, con, app):
-- 
2.30.2

