From edfe9a509eba2ea0a0e9a69370f37d32f76ab6d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 7 Jun 2022 15:20:20 +0200
Subject: [PATCH 1/5] misc: retrieve check types from lingo (#66015)

---
 chrono/utils/lingo.py | 77 +++++++++++++++++++++++++++++++++++++++++++
 tests/settings.py     |  9 +++++
 tests/test_utils.py   | 69 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 155 insertions(+)
 create mode 100644 chrono/utils/lingo.py

diff --git a/chrono/utils/lingo.py b/chrono/utils/lingo.py
new file mode 100644
index 00000000..545a91de
--- /dev/null
+++ b/chrono/utils/lingo.py
@@ -0,0 +1,77 @@
+# chrono - agendas system
+# Copyright (C) 2022  Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 dataclasses
+import json
+
+from django.conf import settings
+from requests.exceptions import RequestException
+
+from chrono.utils.requests_wrapper import requests
+
+
+def is_lingo_enabled():
+    return hasattr(settings, 'KNOWN_SERVICES') and settings.KNOWN_SERVICES.get('lingo')
+
+
+def get_lingo_service():
+    if not is_lingo_enabled():
+        return {}
+    return list(settings.KNOWN_SERVICES.get('lingo').values())[0]
+
+
+def get_lingo_json(path, log_errors=True):
+    lingo_site = get_lingo_service()
+    if lingo_site is None:
+        return
+    try:
+        response = requests.get(
+            path,
+            remote_service=lingo_site,
+            without_user=True,
+            headers={'accept': 'application/json'},
+            log_errors=log_errors,
+        )
+        response.raise_for_status()
+    except RequestException as e:
+        if e.response is not None:
+            try:
+                # return json if available (on 404 responses by example)
+                return e.response.json()
+            except json.JSONDecodeError:
+                pass
+        return
+    return response.json()
+
+
+@dataclasses.dataclass
+class CheckType:
+    slug: str
+    label: str
+    kind: str
+
+
+def get_agenda_check_types(agenda):
+    result = get_lingo_json('api/agenda/%s/check-types/' % agenda.slug)
+    if result is None:
+        return []
+    if result.get('data') is None:
+        return []
+
+    check_types = []
+    for ct in result['data']:
+        check_types.append(CheckType(slug=ct['id'], label=ct['text'], kind=ct['kind']))
+    return check_types
diff --git a/tests/settings.py b/tests/settings.py
index c7257967..50d231bc 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -27,6 +27,15 @@ KNOWN_SERVICES = {
             'backoffice-menu-url': 'http://example.org/manage/',
         }
     },
+    'lingo': {
+        'default': {
+            'title': 'test',
+            'url': 'http://lingo.example.org',
+            'secret': 'chrono',
+            'orig': 'chrono',
+            'backoffice-menu-url': 'http://example.org/manage/',
+        }
+    },
 }
 
 LEGACY_URLS_MAPPING = {'old.org': 'new.org'}
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 1091c0be..77f95d29 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,6 +1,13 @@
 import datetime
+import json
+from unittest import mock
 
+from requests.exceptions import ConnectionError
+from requests.models import Response
+
+from chrono.agendas.models import Agenda
 from chrono.utils.date import get_weekday_index
+from chrono.utils.lingo import CheckType, get_agenda_check_types
 
 
 def test_get_weekday_index():
@@ -21,3 +28,65 @@ def test_get_weekday_index():
         assert get_weekday_index(date.replace(day=28)) == 4
         assert get_weekday_index(date.replace(day=29)) == 5
         assert get_weekday_index(date.replace(day=30)) == 5
+
+
+CHECK_TYPES_DATA = [
+    {'id': 'bar-reason', 'kind': 'presence', 'text': 'Bar reason'},
+    {'id': 'foo-reason', 'kind': 'absence', 'text': 'Foo reason'},
+]
+
+
+class MockedRequestResponse(mock.Mock):
+    status_code = 200
+
+    def json(self):
+        return json.loads(self.content)
+
+
+def test_get_agenda_check_types_no_service(settings):
+    agenda = Agenda(slug='foo')
+
+    settings.KNOWN_SERVICES = {}
+    assert get_agenda_check_types(agenda) == []
+
+    settings.KNOWN_SERVICES = {'other': []}
+    assert get_agenda_check_types(agenda) == []
+
+
+def test_get_agenda_check_types():
+    agenda = Agenda(slug='foo')
+
+    with mock.patch('requests.Session.get') as requests_get:
+        requests_get.side_effect = ConnectionError()
+        assert get_agenda_check_types(agenda) == []
+
+    with mock.patch('requests.Session.get') as requests_get:
+        mock_resp = Response()
+        mock_resp.status_code = 500
+        requests_get.return_value = mock_resp
+        assert get_agenda_check_types(agenda) == []
+
+    with mock.patch('requests.Session.get') as requests_get:
+        mock_resp = Response()
+        mock_resp.status_code = 404
+        requests_get.return_value = mock_resp
+        assert get_agenda_check_types(agenda) == []
+
+    with mock.patch('requests.Session.get') as requests_get:
+        requests_get.return_value = MockedRequestResponse(content=json.dumps({'foo': 'bar'}))
+        assert get_agenda_check_types(agenda) == []
+
+    data = {'data': []}
+    with mock.patch('requests.Session.get') as requests_get:
+        requests_get.return_value = MockedRequestResponse(content=json.dumps(data))
+        assert get_agenda_check_types(agenda) == []
+        assert requests_get.call_args_list[0][0] == ('api/agenda/foo/check-types/',)
+        assert requests_get.call_args_list[0][1]['remote_service']['url'] == 'http://lingo.example.org'
+
+    data = {'data': CHECK_TYPES_DATA}
+    with mock.patch('requests.Session.get') as requests_get:
+        requests_get.return_value = MockedRequestResponse(content=json.dumps(data))
+        assert get_agenda_check_types(agenda) == [
+            CheckType(slug='bar-reason', label='Bar reason', kind='presence'),
+            CheckType(slug='foo-reason', label='Foo reason', kind='absence'),
+        ]
-- 
2.35.1

