From 9c6e35330179a45b3f80e222c298371bd950ce90 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Fri, 18 Mar 2022 13:25:31 +0100
Subject: [PATCH 2/2] plone_restapi: adapt list content sent to Plone (#62943)

---
 passerelle/apps/plone_restapi/models.py | 29 ++++++++++++++++++++-
 tests/test_plone_restapi.py             | 34 +++++++++++++++++++++++++
 2 files changed, 62 insertions(+), 1 deletion(-)

diff --git a/passerelle/apps/plone_restapi/models.py b/passerelle/apps/plone_restapi/models.py
index 8daa691c..0717b9c5 100644
--- a/passerelle/apps/plone_restapi/models.py
+++ b/passerelle/apps/plone_restapi/models.py
@@ -106,19 +106,46 @@ class PloneRestApi(BaseResource, HTTPResource):
     def convert_image_format(self, payload):
         for file_field in payload.values():
             if isinstance(file_field, dict) and file_field.get('filename'):
                 file_field['encoding'] = 'base64'
                 file_field['data'] = file_field['content']
                 file_field['content-type'] = file_field['content_type']
                 del file_field['content']
 
+    def remove_unvaluated_dict_from_list(self, data):
+        """Remove from lists, not empty dicts having all empty string values"""
+
+        def is_not_empty_dict_having_all_empty_values(data):
+            if not isinstance(data, dict):
+                return False
+            if len(data) == 0:
+                return False
+            for value in data.values():
+                if value != '':
+                    return False
+            return True
+
+        if isinstance(data, dict):
+            res = {}
+            for key, value in data.items():
+                res[key] = self.remove_unvaluated_dict_from_list(value)
+            return res
+        if isinstance(data, list):
+            res = []
+            for value in data:
+                tmp = self.remove_unvaluated_dict_from_list(value)
+                if not is_not_empty_dict_having_all_empty_values(tmp):
+                    res.append(tmp)
+            return res
+        return data
+
     def adapt_payload(self, payload):
         self.convert_image_format(payload)
-        return payload
+        return self.remove_unvaluated_dict_from_list(payload)
 
     def adapt_record(
         self,
         record,
         text_template='{{ id }}',
         id_key='UID',
     ):
         self.adapt_id_and_type_plone_attributes(record)
diff --git a/tests/test_plone_restapi.py b/tests/test_plone_restapi.py
index 9de7dcf9..e87af66f 100644
--- a/tests/test_plone_restapi.py
+++ b/tests/test_plone_restapi.py
@@ -391,28 +391,33 @@ def test_create(app, connector, token):
     payload = {
         '@type': 'imio.directory.Contact',
         'title': "Test Entr'ouvert",
         'type': 'organization',
         'schedule': {},
         'topics/0/title': 'Tourisme',
         'topics/0/token': 'tourism',
         'image': {'filename': 'foo.jpg', 'content_type': 'image/jpeg', 'content': '...'},
+        'phones': [
+            {'label': 'numéro principal', 'number': '0123456789', 'type': 'work'},
+            {'label': '', 'number': '', 'type': ''},
+        ],
     }
     with utils.mock_url(url=url, response=json_get_data('fetch'), status_code=201) as mocked:
         resp = app.post_json(endpoint + '?uri=braine-l-alleud&publish=false', params=payload)
         body = json.loads(mocked.handlers[0].call['requests'][1].body)
         assert body['topics'] == [{'title': 'Tourisme', 'token': 'tourism'}]
         assert body['image'] == {
             'filename': 'foo.jpg',
             'content_type': 'image/jpeg',
             'encoding': 'base64',
             'data': '...',
             'content-type': 'image/jpeg',
         }
+        assert body['phones'] == [{'label': 'numéro principal', 'number': '0123456789', 'type': 'work'}]
     assert not resp.json['err']
     assert resp.json['data'] == {
         'uid': 'dccd85d12cf54b6899dff41e5a56ee7f',
         'created': True,
         'review_state': None,
     }
 
 
@@ -466,28 +471,34 @@ def test_update(app, connector, token):
     endpoint = utils.generic_endpoint_url('plone-restapi', 'update', slug=connector.slug)
     assert endpoint == '/plone-restapi/my_connector/update'
     url = connector.service_url + '/braine-l-alleud/dccd85d12cf54b6899dff41e5a56ee7f'
     query_string = '?uri=braine-l-alleud&uid=dccd85d12cf54b6899dff41e5a56ee7f'
     payload = {
         'title': 'Test update',
         'topics/0/token': 'social',
         'image': {'filename': 'foo.jpg', 'content_type': 'image/jpeg', 'content': '...'},
+        'phones': [
+            {'label': '', 'number': '', 'type': ''},
+            {'label': '', 'number': '', 'type': ''},
+        ],
     }
     with utils.mock_url(url=url, response='', status_code=204) as mocked:
         resp = app.post_json(endpoint + query_string, params=payload)
         body = json.loads(mocked.handlers[0].call['requests'][1].body)
         assert body['topics'] == [{'token': 'social'}]
         assert body['image'] == {
             'filename': 'foo.jpg',
             'content_type': 'image/jpeg',
             'encoding': 'base64',
             'data': '...',
             'content-type': 'image/jpeg',
         }
+        assert body['phones'] == []
+
     assert not resp.json['err']
     assert resp.json['data'] == {'uid': 'dccd85d12cf54b6899dff41e5a56ee7f', 'updated': True}
 
 
 def test_update_wrong_payload(app, connector, token):
     endpoint = utils.generic_endpoint_url('plone-restapi', 'update', slug=connector.slug)
     assert endpoint == '/plone-restapi/my_connector/update'
     url = connector.service_url + '/braine-l-alleud/dccd85d12cf54b6899dff41e5a56ee7f'
@@ -657,8 +668,31 @@ def test_query_q_using_id(app, query, token):
         resp = app.get(endpoint, params=params)
     assert qs == {
         'UID': '9fbb2afd499e465983434f974fce8404',
         'fullobjects': 'y',
     }
     assert len(resp.json['data']) == 1
     assert resp.json['data'][0]['text'] == "Académie de Musique de Braine-l'Alleud (imio.directory.Contact)"
     assert resp.json['meta'] == {'label': 'demo query', 'description': "Annuaire de Braine-l'Alleud"}
+
+
+@pytest.mark.parametrize(
+    'in_data, out_data',
+    [
+        [{}, 'same'],
+        [[], 'same'],
+        [[''], 'same'],
+        [[{}], 'same'],
+        [{'k1': ''}, 'same'],
+        [{'k1': []}, 'same'],
+        [{'k1': '', 'k2': 'v2'}, 'same'],
+        [[{'k1': ''}], []],
+        [[{'k1': ''}, {}], [{}]],
+        [[{'k1': []}], 'same'],
+        [[{'k1': [{'k11': ''}]}], [{'k1': []}]],
+        [[{'k1': [{'k11': ''}, {}]}], [{'k1': [{}]}]],
+    ],
+)
+def test_remove_unvaluated_dict_from_list(connector, in_data, out_data):
+    if out_data == 'same':
+        out_data = in_data
+    assert connector.remove_unvaluated_dict_from_list(in_data) == out_data
-- 
2.30.2

