From dba57a0e0a7a35eeb26ac1232f64624a258cb53b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 12 Dec 2019 16:20:41 +0100
Subject: [PATCH] toulouse_axel: bool mapping (#38464)

---
 passerelle/contrib/toulouse_axel/models.py | 51 +++++++++++-
 tests/test_toulouse_axel_schema.py         | 90 ++++++++++++++++++++++
 2 files changed, 139 insertions(+), 2 deletions(-)
 create mode 100644 tests/test_toulouse_axel_schema.py

diff --git a/passerelle/contrib/toulouse_axel/models.py b/passerelle/contrib/toulouse_axel/models.py
index 9f22fb09..55d0dfcc 100644
--- a/passerelle/contrib/toulouse_axel/models.py
+++ b/passerelle/contrib/toulouse_axel/models.py
@@ -43,7 +43,9 @@ BASE_XSD_PATH = os.path.join(os.path.dirname(__file__), 'xsd')
 class AxelSchema(JSONSchemaFromXMLSchema):
     type_map = {
         '{urn:AllAxelTypes}DATEREQUIREDType': 'date',
-        '{urn:AllAxelTypes}DATEType': 'date',
+        '{urn:AllAxelTypes}DATEType': 'date_optional',
+        '{urn:AllAxelTypes}OUINONREQUIREDType': 'bool',
+        '{urn:AllAxelTypes}OUINONType': 'bool_optional',
     }
 
     @classmethod
@@ -56,12 +58,57 @@ class AxelSchema(JSONSchemaFromXMLSchema):
     def encode_date(self, obj):
         return datetime.datetime.strptime(obj, '%Y-%m-%d').strftime('%d/%m/%Y')
 
+    def encode_date_optional(self, obj):
+        return self.encode_date(obj)
+
     def decode_date(self, data):
+        value = datetime.datetime.strptime(data.text, '%d/%m/%Y').strftime('%Y-%m-%d')
+        return xmlschema.ElementData(tag=data.tag, text=value, content=data.content, attributes=data.attributes)
+
+    def decode_date_optional(self, data):
         if not data.text:
             return data
-        value = datetime.datetime.strptime(data.text, '%d/%m/%Y').strftime('%Y-%m-%d')
+        return self.decode_date(data)
+
+    @classmethod
+    def schema_bool(cls):
+        return {
+            'oneOf': [
+                {'type': 'boolean'},
+                {
+                    'type': 'string',
+                    'pattern': '(?i)(oui|non|true|false|1|0)',
+                }
+            ]
+        }
+
+    def encode_bool(self, obj):
+        if obj is True or str(obj).lower() in ['true', 'oui', '1']:
+            return 'OUI'
+        if obj is False or str(obj).lower() in ['false', 'non', '0']:
+            return 'NON'
+        return ''
+
+    def decode_bool(self, data):
+        value = False
+        if data.text.lower() == 'oui':
+            value = True
         return xmlschema.ElementData(tag=data.tag, text=value, content=data.content, attributes=data.attributes)
 
+    @classmethod
+    def schema_bool_optional(cls):
+        schema_bool_optional = cls.schema_bool()
+        schema_bool_optional['oneOf'].append({'type': 'string', 'enum': ['']})
+        return schema_bool_optional
+
+    def encode_bool_optional(self, obj):
+        return self.encode_bool(obj)
+
+    def decode_bool_optional(self, data):
+        if not data.text:
+            return data
+        return self.decode_bool(data)
+
 
 class AxelError(Exception):
     pass
diff --git a/tests/test_toulouse_axel_schema.py b/tests/test_toulouse_axel_schema.py
new file mode 100644
index 00000000..6302b824
--- /dev/null
+++ b/tests/test_toulouse_axel_schema.py
@@ -0,0 +1,90 @@
+# passerelle - uniform access to multiple data sources and services
+# Copyright (C) 2019 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 os
+import pytest
+
+from passerelle.contrib.toulouse_axel.models import AxelSchema
+
+XSD_BASE_DIR = os.path.join(
+    os.path.dirname(os.path.abspath(__file__)),
+    '../passerelle/contrib/toulouse_axel/xsd')
+
+
+@pytest.mark.parametrize('date_type', ['DATEREQUIREDType', 'DATEType'])
+def test_date_mapping(date_type):
+    xsd = """<?xml version="1.0" encoding="utf-8" ?>
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:all="urn:AllAxelTypes">
+    <xsd:import schemaLocation="{path}/AllAxelTypes.xsd" namespace="urn:AllAxelTypes"  />
+    <xsd:complexType name="PORTAILType">
+        <xsd:sequence>
+            <xsd:element name="DATE" type="all:{date_type}"/>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:element name="PORTAIL" type="PORTAILType"/>
+</xsd:schema>""".format(path=XSD_BASE_DIR, date_type=date_type)
+
+    schema = AxelSchema(xsd, 'PORTAIL')
+    xml_data = schema.encode({'PORTAIL': {'DATE': '2019-12-12'}})
+    assert xml_data.find('DATE').text == '12/12/2019'
+
+    json_data = schema.decode(xml_data)
+    assert json_data['DATE'] == '2019-12-12'
+
+
+@pytest.mark.parametrize('bool_type', ['OUINONREQUIREDType', 'OUINONType'])
+@pytest.mark.parametrize('value, expected, py_expected', [
+    ('OUI', 'OUI', True),
+    ('oui', 'OUI', True),
+    ('Oui', 'OUI', True),
+    ('TRUE', 'OUI', True),
+    ('true', 'OUI', True),
+    ('True', 'OUI', True),
+    (True, 'OUI', True),
+    ('1', 'OUI', True),
+    ('NON', 'NON', False),
+    ('non', 'NON', False),
+    ('Non', 'NON', False),
+    ('FALSE', 'NON', False),
+    ('false', 'NON', False),
+    ('False', 'NON', False),
+    (False, 'NON', False),
+    ('0', 'NON', False),
+    ('FOOBAR', '', None),
+    ('42', '', None),
+])
+def test_bool_mapping(bool_type, value, expected, py_expected):
+    if expected == '' and bool_type == 'OUINONREQUIREDType':
+        # required, can't be empty
+        return
+
+    xsd = """<?xml version="1.0" encoding="utf-8" ?>
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:all="urn:AllAxelTypes">
+    <xsd:import schemaLocation="{path}/AllAxelTypes.xsd" namespace="urn:AllAxelTypes"  />
+    <xsd:complexType name="PORTAILType">
+        <xsd:sequence>
+            <xsd:element name="BOOL" type="all:{bool_type}"/>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:element name="PORTAIL" type="PORTAILType"/>
+</xsd:schema>""".format(path=XSD_BASE_DIR, bool_type=bool_type)
+
+    schema = AxelSchema(xsd, 'PORTAIL')
+    xml_data = schema.encode({'PORTAIL': {'BOOL': value}})
+    assert xml_data.find('BOOL').text == expected
+
+    json_data = schema.decode(xml_data)
+    assert json_data['BOOL'] is py_expected
-- 
2.20.1

