From 932907630bf8d57cc2b6edfdeaf0c9ee19fac6ce Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Fri, 13 Jan 2023 20:09:08 +0100
Subject: [PATCH 3/3] toulouse-maelis: manage XML soap fault detail content
 (#73411)

---
 passerelle/utils/soap.py      |  3 +++
 tests/test_toulouse_maelis.py | 36 ++++++++++++++++++++++++++++++++---
 2 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/passerelle/utils/soap.py b/passerelle/utils/soap.py
index 6e6e6798..c026d471 100644
--- a/passerelle/utils/soap.py
+++ b/passerelle/utils/soap.py
@@ -11,16 +11,17 @@
 # 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/>.
 
 from urllib import parse as urlparse
 
+from lxml import etree
 from requests import RequestException
 from zeep import Client
 from zeep.cache import InMemoryCache
 from zeep.exceptions import Error as ZeepError
 from zeep.exceptions import Fault, TransportError
 from zeep.proxy import OperationProxy, ServiceProxy
 from zeep.transports import Transport
 
@@ -43,16 +44,18 @@ class SOAPServiceUnreachable(SOAPError):
             },
         )
 
 
 class SOAPFault(SOAPError):
     log_error = False
 
     def __init__(self, client, fault):
+        if hasattr(fault, 'detail') and isinstance(fault.detail, etree._Element):
+            fault.detail = etree.tostring(fault.detail).decode()
         super().__init__(
             f'SOAP service at {client.wsdl.location} returned an error "{fault.message or fault.code}"',
             data={
                 'soap_fault': fault.__dict__,
             },
         )
 
 
diff --git a/tests/test_toulouse_maelis.py b/tests/test_toulouse_maelis.py
index b44555c4..dc0bb46d 100644
--- a/tests/test_toulouse_maelis.py
+++ b/tests/test_toulouse_maelis.py
@@ -188,16 +188,25 @@ def test_call_with_wrong_credentials(family_service, con):
         match='The security token could not be authenticated or authorized',
     ) as e:
         con.call('Family', 'isWSRunning')
     assert e.value.__dict__ == {
         'err': 1,
         'log_error': False,
         'http_status': 200,
         'err_code': 'Family-isWSRunning-ns1:FailedAuthentication',
+        'data': {
+            'soap_fault': {
+                'message': 'The security token could not be authenticated or authorized',
+                'code': 'ns1:FailedAuthentication',
+                'actor': None,
+                'detail': None,
+                'subcodes': None,
+            }
+        },
     }
 
 
 def test_call(family_service, con):
     family_service.add_soap_response('isWSRunning', get_xml_file('R_is_ws_running.xml') % b'true')
     resp = con.call('Family', 'isWSRunning')
     assert resp is True
 
@@ -4416,21 +4425,42 @@ def test_read_school_list_address_and_level(site_service, con, app):
     assert len(resp.json['data']) == 8
     for item in resp.json['data']:
         assert 'id' in item
         assert 'text' in item
 
 
 def test_read_school_list_address_and_level_soap_error(site_service, con, app):
     site_service.add_soap_response(
-        'readSchoolForAdressAndLevel', get_xml_file('R_read_school_for_adress_and_level_soap_error.xml'), status=500,
+        'readSchoolForAdressAndLevel',
+        get_xml_file('R_read_school_for_adress_and_level_soap_error.xml'),
+        status=500,
     )
     url = get_endpoint('read-schools-for-address-and-level')
-    with pytest.raises(TypeError, match='Object of type _Element is not JSON serializable') as e:
-        resp = app.get(url, params={'id_street': '', 'num': '', 'year': ''})
+    resp = app.get(url, params={'id_street': '', 'num': '', 'year': ''})
+    assert resp.json['err'] == 'Site-readSchoolForAdressAndLevel-soap:Server'
+    assert (
+        resp.json['err_desc']
+        == 'SOAP service at https://example.org/SiteService?wsdl returned an error "E19 : La voie est obligatoire"'
+    )
+    assert resp.json['data'] == {
+        'soap_fault': {
+            'message': 'E19 : La voie est obligatoire',
+            'code': 'soap:Server',
+            'actor': None,
+            'detail': '<detail xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">'
+            + '\n        <ns1:MaelisSiteException xmlns:ns1="site.ws.maelis.sigec.com">'
+            + '\n          <message xmlns:ns2="site.ws.maelis.sigec.com">E19 : La voie est obligatoire</message>'
+            + '\n          <code xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
+            + ' xmlns:ns2="site.ws.maelis.sigec.com" xsi:nil="true"/>'
+            + '\n        </ns1:MaelisSiteException>\n      </detail>'
+            + '\n    ',
+            'subcodes': None,
+        }
+    }
 
 
 def test_read_school_list_child_and_level(family_service, con, app):
     family_service.add_soap_response(
         'readSchoolForChildAndLevel', get_xml_file('R_read_school_for_child_and_level.xml')
     )
     url = get_endpoint('read-schools-for-child-and-level')
     resp = app.get(url, params={'child_id': '190115', 'year': '2023'})
-- 
2.30.2

