Repository: fossasia/open-event-server

Issue:
Order should fail to transition to pending state if attendee info is incomplete
Right now, since there was no validation on attendee form, an attendee could submit dummy information and change order status to pending and complete the order. After #7068, order validation should be put into place so that no order can be completed without correct attendee info state


Discussion:

Resolving patch:
diff --git a/app/api/orders.py b/app/api/orders.py
--- a/app/api/orders.py
+++ b/app/api/orders.py
@@ -12,6 +12,7 @@
 from marshmallow_jsonapi.flask import Schema
 
 from app.api.bootstrap import api
+from app.api.helpers.custom_forms import validate_custom_form_constraints_request
 from app.api.data_layers.ChargesLayer import ChargesLayer
 from app.api.helpers.db import safe_query, safe_query_by_id, safe_query_kwargs, save_to_db
 from app.api.helpers.errors import (
@@ -46,6 +47,7 @@
 from app.api.helpers.ticketing import validate_discount_code, validate_ticket_holders
 from app.api.helpers.utilities import dasherize, require_relationship
 from app.api.schema.orders import OrderSchema
+from app.api.schema.attendees import AttendeeSchema
 from app.models import db
 from app.models.order import Order, OrderTicket, get_updatable_fields
 from app.models.ticket_holder import TicketHolder
@@ -379,6 +381,13 @@ def before_update_object(self, order, data, view_kwargs):
         :param view_kwargs:
         :return:
         """
+        if data.get('status') in ['pending', 'placed', 'completed']:
+            attendees = order.ticket_holders
+            for attendee in attendees:
+                validate_custom_form_constraints_request(
+                    'attendee', AttendeeSchema, attendee, {}
+                )
+
         if data.get('amount') and (
             data.get('is_billing_enabled') or order.event.is_billing_info_mandatory
         ):


Test patch:
diff --git a/tests/all/integration/api/helpers/order/test_edit_order.py b/tests/all/integration/api/helpers/order/test_edit_order.py
--- a/tests/all/integration/api/helpers/order/test_edit_order.py
+++ b/tests/all/integration/api/helpers/order/test_edit_order.py
@@ -1,6 +1,8 @@
 import json
 
 from app.models.order import Order
+from app.models.custom_form import CustomForms
+from app.models.ticket_holder import TicketHolder
 from tests.factories.attendee import AttendeeSubFactory
 from tests.factories.event import EventFactoryBasic
 from tests.factories.order import OrderSubFactory
@@ -91,3 +93,162 @@ def test_ignore_on_order_event_update(client, db, user, jwt):
     db.session.refresh(order)
     assert response.status_code == 200
     assert order.event == order_event
+
+
+def get_complex_custom_form_order(db, user):
+    order = OrderSubFactory(amount=234, status='initializing', user=user)
+    attendee = AttendeeSubFactory(order=order)
+
+    CustomForms(
+        event=attendee.event,
+        form='attendee',
+        field_identifier='whatUniversity',
+        name='what university',
+        type='text',
+        is_complex=True,
+        is_included=True,
+        is_required=True,
+    )
+
+    CustomForms(
+        event=attendee.event,
+        form='attendee',
+        field_identifier='whatCollege',
+        name='what college',
+        type='text',
+        is_complex=True,
+        is_included=True,
+        is_required=True,
+    )
+
+    CustomForms(
+        event=attendee.event,
+        form='attendee',
+        field_identifier='naamBatao',
+        name='naam batao',
+        type='text',
+        is_complex=True,
+        is_included=True,
+        is_required=False,
+    )
+
+    db.session.commit()
+
+    return str(order.id)
+
+
+def test_order_pending_incomplete_complex_custom_form(client, db, user, jwt):
+    order_id = get_complex_custom_form_order(db, user)
+    order = Order.query.get(order_id)
+
+    data = json.dumps(
+        {
+            "data": {
+                "attributes": {"status": "pending", "order-notes": "do it pending"},
+                "type": "order",
+                "id": order_id,
+            }
+        }
+    )
+
+    response = client.patch(
+        f'/v1/orders/{order_id}',
+        content_type='application/vnd.api+json',
+        headers=jwt,
+        data=data,
+    )
+
+    db.session.refresh(order)
+
+    assert response.status_code == 422
+    assert order.status != "pending"
+    assert order.order_notes != "do it pending"
+    assert json.loads(response.data) == {
+        'errors': [
+            {
+                'status': 422,
+                'source': {'pointer': '/data/attributes'},
+                'title': 'Unprocessable Entity',
+                'detail': "Missing required fields ['what_college', 'what_university']",
+            }
+        ],
+        'jsonapi': {'version': '1.0'},
+    }
+
+
+def test_order_placed_incomplete_complex_custom_form(client, db, user, jwt):
+    order_id = get_complex_custom_form_order(db, user)
+    order = Order.query.get(order_id)
+
+    data = json.dumps(
+        {
+            "data": {
+                "attributes": {"status": "placed", "order-notes": "just place it"},
+                "type": "order",
+                "id": order_id,
+            }
+        }
+    )
+
+    response = client.patch(
+        f'/v1/orders/{order_id}',
+        content_type='application/vnd.api+json',
+        headers=jwt,
+        data=data,
+    )
+
+    db.session.refresh(order)
+
+    assert response.status_code == 422
+    assert order.status != "placed"
+    assert order.order_notes != "just place it"
+    assert json.loads(response.data) == {
+        'errors': [
+            {
+                'status': 422,
+                'source': {'pointer': '/data/attributes'},
+                'title': 'Unprocessable Entity',
+                'detail': "Missing required fields ['what_college', 'what_university']",
+            }
+        ],
+        'jsonapi': {'version': '1.0'},
+    }
+
+
+def test_order_pending_complete_complex_custom_form(client, db, user, jwt):
+    order_id = get_complex_custom_form_order(db, user)
+    order = Order.query.get(order_id)
+
+    attendee = TicketHolder.query.filter_by(order_id=order.id).first()
+    attendee.complex_field_values = {
+        "what_college": "Zakir Hussain College",
+        "what_university": "Aligarh Muslim University",
+    }
+    db.session.commit()
+
+    data = json.dumps(
+        {
+            "data": {
+                "attributes": {"status": "pending", "order-notes": "do it pending"},
+                "type": "order",
+                "id": order_id,
+            }
+        }
+    )
+
+    response = client.patch(
+        f'/v1/orders/{order_id}',
+        content_type='application/vnd.api+json',
+        headers=jwt,
+        data=data,
+    )
+
+    db.session.refresh(order)
+
+    json_response = json.loads(response.data)
+
+    assert response.status_code == 200
+    assert order.status == "pending"
+    assert order.order_notes == "do it pending"
+    assert json_response['data']['attributes']['status'] == "pending"
+    assert json_response['data']['attributes']['order-notes'] == "do it pending"