Repository: fossasia/open-event-server

Issue:
Test POST API with minimum payload
We should write testcases where POST APIs of events, microlocations etc are tested with minimum payloads meaning only those fields which are required are set in the payload. 
This will help finding bugs. Many of the recent bugs I see are because of this. 



Discussion:

Resolving patch:
diff --git a/open_event/api/events.py b/open_event/api/events.py
--- a/open_event/api/events.py
+++ b/open_event/api/events.py
@@ -34,8 +34,8 @@
 
 SOCIAL_LINK = api.model('SocialLink', {
     'id': fields.Integer(),
-    'name': fields.String(),
-    'link': fields.String()
+    'name': fields.String(required=True),
+    'link': fields.String(required=True)
 })
 
 SOCIAL_LINK_POST = api.clone('SocialLinkPost', SOCIAL_LINK)
diff --git a/open_event/api/sessions.py b/open_event/api/sessions.py
--- a/open_event/api/sessions.py
+++ b/open_event/api/sessions.py
@@ -52,8 +52,8 @@
     'short_abstract': fields.String(),
     'long_abstract': fields.String(),
     'comments': fields.String(),
-    'start_time': fields.DateTime(),
-    'end_time': fields.DateTime(),
+    'start_time': fields.DateTime(required=True),
+    'end_time': fields.DateTime(required=True),
     'track': fields.Nested(SESSION_TRACK, allow_null=True),
     'speakers': fields.List(fields.Nested(SESSION_SPEAKER)),
     'language': SessionLanguageField(),
@@ -84,7 +84,6 @@
 del SESSION_POST['session_type']
 
 
-
 # Create DAO
 
 class SessionTypeDAO(ServiceDAO):
@@ -125,7 +124,7 @@ def fix_payload_post(self, event_id, data):
             SessionTypeModel, data.get('session_type_id'), event_id)
         data['event_id'] = event_id
         data['speakers'] = InstrumentedList(
-            SpeakerModel.query.get(_) for _ in data['speaker_ids']
+            SpeakerModel.query.get(_) for _ in data.get('speaker_ids', [])
             if self.get_object(SpeakerModel, _, event_id) is not None
         )
         data = self._delete_fields(data)
diff --git a/open_event/api/speakers.py b/open_event/api/speakers.py
--- a/open_event/api/speakers.py
+++ b/open_event/api/speakers.py
@@ -19,20 +19,20 @@
 
 SPEAKER = api.model('Speaker', {
     'id': fields.Integer(required=True),
-    'name': fields.String(),
+    'name': fields.String(required=True),
     'photo': fields.Upload(),
     'short_biography': fields.String(),
     'long_biography': fields.String(),
-    'email': fields.Email(),
+    'email': fields.Email(required=True),
     'mobile': fields.String(),
     'website': fields.Uri(),
     'twitter': fields.String(),  # not sure for now whether uri or string field
     'facebook': fields.String(),
     'github': fields.String(),
     'linkedin': fields.String(),
-    'organisation': fields.String(),
+    'organisation': fields.String(required=True),
     'position': fields.String(),
-    'country': fields.String(),
+    'country': fields.String(required=True),
     'sessions': fields.List(fields.Nested(SPEAKER_SESSION)),
 })
 


Test patch:
diff --git a/tests/api/test_post_api.py b/tests/api/test_post_api.py
--- a/tests/api/test_post_api.py
+++ b/tests/api/test_post_api.py
@@ -8,6 +8,13 @@
 from tests.auth_helper import register
 from open_event import current_app as app
 
+from open_event.api.events import EVENT_POST, SOCIAL_LINK_POST
+from open_event.api.tracks import TRACK_POST
+from open_event.api.microlocations import MICROLOCATION_POST
+from open_event.api.sessions import SESSION_POST, SESSION_TYPE_POST
+from open_event.api.speakers import SPEAKER_POST
+from open_event.api.sponsors import SPONSOR_POST
+
 
 class TestPostApiBase(OpenEventTestCase):
     """
@@ -42,7 +49,7 @@ class TestPostApi(TestPostApiBase):
     Test POST APIs against 401 (unauthorized) and
     201 (successful) status codes
     """
-    def _test_model(self, name, data, checks=[]):
+    def _test_model(self, name, data, path=None, checks=[]):
         """
         Tests -
         1. Without login, try to do a POST request and catch 401 error
@@ -50,7 +57,8 @@ def _test_model(self, name, data, checks=[]):
         Param:
             checks - list of strings to assert in successful response data
         """
-        path = get_path() if name == 'event' else get_path(1, name + 's')
+        if not path:
+            path = get_path() if name == 'event' else get_path(1, name + 's')
         response = self.post_request(path, data)
         self.assertEqual(401, response.status_code, msg=response.data)
         # login and send the request again
@@ -58,15 +66,14 @@ def _test_model(self, name, data, checks=[]):
         response = self.post_request(path, data)
         self.assertEqual(201, response.status_code, msg=response.data)
         self.assertIn('location', response.headers)
-        self.assertIn('Test' + str(name).title(), response.data)
+        self.assertIn('Test' + name[0].upper() + name[1:], response.data)
         for string in checks:
             self.assertIn(string, response.data, msg=string)
-        return response
 
     def test_event_api(self):
         self._test_model(
             'event', POST_EVENT_DATA,
-            ['test@example.com', 'Test licence']
+            checks=['test@example.com', 'Test licence']
         )
 
     def test_track_api(self):
@@ -90,30 +97,65 @@ def test_sponsor_api(self):
         self._test_model('sponsor', POST_SPONSOR_DATA)
 
     def test_social_link_api(self):
-        self._login_user()
-        # Create a social link
-        path = get_path(1, 'links')
-        data = POST_SOCIAL_LINK_DATA
-        response = self.post_request(path, data)
-        self.assertEqual(response.status_code, 201)
-
-        path = get_path(1, 'links')
-        response = self.app.get(path)
-        self.assertEqual(response.status_code, 200)
-        self.assertIn('TestSocialLink', response.data)
+        self._test_model(
+            'socialLink', POST_SOCIAL_LINK_DATA, path=get_path(1, 'links')
+        )
 
     def test_session_type_api(self):
+        self._test_model(
+            'sessionType', POST_SESSION_TYPE_DATA,
+            path=get_path(1, 'sessions', 'types')
+        )
+
+
+class TestPostApiMin(TestPostApiBase):
+    """
+    Test POST API with minimum payload
+    Only required payloads are kept
+    """
+    def _test_model(self, name, data, api_model, path=None):
+        # strip data
+        data = data.copy()
+        for i in api_model:
+            if not api_model[i].required:
+                data.pop(i, None)
+        # test
+        if not path:
+            path = get_path() if name == 'event' else get_path(1, name + 's')
         self._login_user()
-        # Create a session type
-        path = get_path(1, 'sessions', 'types')
-        data = POST_SESSION_TYPE_DATA
         response = self.post_request(path, data)
-        self.assertEqual(response.status_code, 201)
+        self.assertEqual(201, response.status_code, msg=response.data)
+        self.assertIn('Test' + name[0].upper() + name[1:], response.data)
+
+    def test_event_api(self):
+        self._test_model('event', POST_EVENT_DATA, EVENT_POST)
+
+    def test_track_api(self):
+        self._test_model('track', POST_TRACK_DATA, TRACK_POST)
+
+    def test_microlocation_api(self):
+        self._test_model('microlocation', POST_MICROLOCATION_DATA, MICROLOCATION_POST)
+
+    def test_session_api(self):
+        self._test_model('session', POST_SESSION_DATA, SESSION_POST)
+
+    def test_speaker_api(self):
+        self._test_model('speaker', POST_SPEAKER_DATA, SPEAKER_POST)
+
+    def test_sponsor_api(self):
+        self._test_model('sponsor', POST_SPONSOR_DATA, SPONSOR_POST)
+
+    def test_social_link_api(self):
+        self._test_model(
+            'socialLink', POST_SOCIAL_LINK_DATA,
+            SOCIAL_LINK_POST, path=get_path(1, 'links')
+        )
 
-        path = get_path(1, 'sessions', 'types')
-        response = self.app.get(path)
-        self.assertEqual(response.status_code, 200)
-        self.assertIn('TestSessionType', response.data)
+    def test_session_type_api(self):
+        self._test_model(
+            'sessionType', POST_SESSION_TYPE_DATA,
+            SESSION_TYPE_POST, path=get_path(1, 'sessions', 'types')
+        )
 
 
 if __name__ == '__main__':