diff --git a/src/openapi.yaml b/src/openapi.yaml index e3a04eeee6..00858e2067 100644 --- a/src/openapi.yaml +++ b/src/openapi.yaml @@ -5619,11 +5619,54 @@ paths: $ref: '#/components/headers/X-Is-Form-Designer' Content-Language: $ref: '#/components/headers/Content-Language' + /api/v2/variables/registration: + get: + operationId: variables_registration_list + description: List the registration static variables that will be associated + with every form + summary: Get registration static variables + tags: + - variables + security: + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/FormVariable' + description: '' + headers: + X-Session-Expires-In: + $ref: '#/components/headers/X-Session-Expires-In' + X-CSRFToken: + $ref: '#/components/headers/X-CSRFToken' + X-Is-Form-Designer: + $ref: '#/components/headers/X-Is-Form-Designer' + Content-Language: + $ref: '#/components/headers/Content-Language' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/Exception' + description: '' + headers: + X-Session-Expires-In: + $ref: '#/components/headers/X-Session-Expires-In' + X-CSRFToken: + $ref: '#/components/headers/X-CSRFToken' + X-Is-Form-Designer: + $ref: '#/components/headers/X-Is-Form-Designer' + Content-Language: + $ref: '#/components/headers/Content-Language' /api/v2/variables/static: get: operationId: variables_static_list - description: List the static data that will be associated with every form - summary: Get static data + description: List the static variables that will be associated with every form + summary: Get static variables tags: - variables security: diff --git a/src/openforms/forms/tests/variables/test_variables_in_logic_trigger.py b/src/openforms/forms/tests/variables/test_variables_in_logic_trigger.py index 4dcd61a31c..4ac5f31fa3 100644 --- a/src/openforms/forms/tests/variables/test_variables_in_logic_trigger.py +++ b/src/openforms/forms/tests/variables/test_variables_in_logic_trigger.py @@ -220,7 +220,9 @@ def test_static_variable_in_trigger(self): self.client.force_authenticate(user=user) url = reverse("api:form-logic-rules", kwargs={"uuid_or_slug": form.uuid}) - with patch("openforms.variables.service.register", new=register): + with patch( + "openforms.variables.service.static_variables_registry", new=register + ): response = self.client.put(url, data=form_logic_data) self.assertEqual(status.HTTP_200_OK, response.status_code) diff --git a/src/openforms/registrations/base.py b/src/openforms/registrations/base.py index a4a52a3d8b..3ba4e0cfa9 100644 --- a/src/openforms/registrations/base.py +++ b/src/openforms/registrations/base.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from abc import ABC, abstractmethod from dataclasses import dataclass from typing import TYPE_CHECKING @@ -8,8 +10,10 @@ from openforms.utils.mixins import JsonSchemaSerializerMixin if TYPE_CHECKING: + from openforms.forms.models import FormVariable from openforms.submissions.models import Submission + SerializerCls = type[serializers.Serializer] @@ -42,16 +46,14 @@ class BasePlugin(ABC, AbstractBasePlugin): """ @abstractmethod - def register_submission( - self, submission: "Submission", options: dict - ) -> dict | None: + def register_submission(self, submission: Submission, options: dict) -> dict | None: raise NotImplementedError() - def update_payment_status(self, submission: "Submission", options: dict): + def update_payment_status(self, submission: Submission, options: dict): raise NotImplementedError() def pre_register_submission( - self, submission: "Submission", options: dict + self, submission: Submission, options: dict ) -> PreRegistrationResult: """Perform any tasks before registering the submission @@ -65,3 +67,9 @@ def get_custom_templatetags_libraries(self) -> list[str]: Return a list of custom templatetags libraries that will be added to the 'sandboxed' Django templates backend. """ return [] + + def get_variables(self) -> list[FormVariable]: + """ + Return the static variables for this registration plugin. + """ + return [] diff --git a/src/openforms/registrations/contrib/objects_api/migrations/0013_objectsapiregistrationdata.py b/src/openforms/registrations/contrib/objects_api/migrations/0013_objectsapiregistrationdata.py new file mode 100644 index 0000000000..aa45209fbc --- /dev/null +++ b/src/openforms/registrations/contrib/objects_api/migrations/0013_objectsapiregistrationdata.py @@ -0,0 +1,70 @@ +# Generated by Django 4.2.10 on 2024-03-13 10:39 + +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("submissions", "0004_auto_20231128_1536"), + ("registrations_objects_api", "0012_fill_required_fields"), + ] + + operations = [ + migrations.CreateModel( + name="ObjectsAPIRegistrationData", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "pdf_url", + models.URLField( + blank=True, + help_text="The PDF URL of the document on the Documents API.", + verbose_name="pdf url", + ), + ), + ( + "csv_url", + models.URLField( + blank=True, + help_text="The CSV URL of the document on the Documents API.", + verbose_name="csv url", + ), + ), + ( + "attachment_urls", + django.contrib.postgres.fields.ArrayField( + base_field=models.URLField( + help_text="The attachment URL on the Documents API.", + verbose_name="attachment url", + ), + blank=True, + default=list, + help_text="The list of attachment URLs on the Documents API.", + size=None, + verbose_name="attachment urls", + ), + ), + ( + "submission", + models.OneToOneField( + help_text="The submission linked to the registration data.", + on_delete=django.db.models.deletion.CASCADE, + related_name="objects_api_registration_data", + to="submissions.submission", + verbose_name="submission", + ), + ), + ], + ), + ] diff --git a/src/openforms/registrations/contrib/objects_api/models.py b/src/openforms/registrations/contrib/objects_api/models.py index 9e0d7e920e..5449920690 100644 --- a/src/openforms/registrations/contrib/objects_api/models.py +++ b/src/openforms/registrations/contrib/objects_api/models.py @@ -1,3 +1,4 @@ +from django.contrib.postgres.fields import ArrayField from django.core.exceptions import ValidationError from django.db import models from django.template.loader import render_to_string @@ -195,3 +196,38 @@ def apply_defaults_to(self, options): options["payment_status_update_json"] = self.payment_status_update_json options.setdefault("auteur", "Aanvrager") + + +class ObjectsAPIRegistrationData(models.Model): + """Holds the temporary data available when registering a submission to the Objects API. + + When starting the submission registration, this model is first populated. The Objects API + registration variables can then safely make use of this model to provide their data. + """ + + submission = models.OneToOneField( + "submissions.Submission", + on_delete=models.CASCADE, + verbose_name=_("submission"), + help_text=_("The submission linked to the registration data."), + related_name="objects_api_registration_data", + ) + pdf_url = models.URLField( + _("pdf url"), + help_text=_("The PDF URL of the document on the Documents API."), + blank=True, + ) + csv_url = models.URLField( + _("csv url"), + help_text=_("The CSV URL of the document on the Documents API."), + blank=True, + ) + attachment_urls = ArrayField( + models.URLField( + _("attachment url"), help_text=_("The attachment URL on the Documents API.") + ), + verbose_name=_("attachment urls"), + help_text=_("The list of attachment URLs on the Documents API."), + blank=True, + default=list, + ) diff --git a/src/openforms/registrations/contrib/objects_api/plugin.py b/src/openforms/registrations/contrib/objects_api/plugin.py index fa74f37135..20645458cf 100644 --- a/src/openforms/registrations/contrib/objects_api/plugin.py +++ b/src/openforms/registrations/contrib/objects_api/plugin.py @@ -1,6 +1,8 @@ +from __future__ import annotations + import logging from functools import partial -from typing import Any +from typing import TYPE_CHECKING, Any from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -8,8 +10,8 @@ from typing_extensions import override from openforms.registrations.utils import execute_unless_result_exists -from openforms.submissions.models import Submission from openforms.utils.date import get_today +from openforms.variables.service import get_static_variables from ...base import BasePlugin from ...registry import register @@ -17,9 +19,15 @@ from .client import get_objects_client from .config import ObjectsAPIOptionsSerializer from .models import ObjectsAPIConfig +from .registration_variables import register as variables_registry from .submission_registration import HANDLER_MAPPING from .typing import RegistrationOptions +if TYPE_CHECKING: + from openforms.forms.models import FormVariable + from openforms.submissions.models import Submission + + PLUGIN_IDENTIFIER = "objects_api" logger = logging.getLogger(__name__) @@ -58,6 +66,8 @@ def register_submission( handler = HANDLER_MAPPING[options["version"]] + handler.save_registration_data(submission, options) + object_data = handler.get_object_data( submission=submission, options=options, @@ -129,3 +139,7 @@ def update_payment_status( headers={"Content-Crs": "EPSG:4326"}, ) response.raise_for_status() + + @override + def get_variables(self) -> list[FormVariable]: + return get_static_variables(variables_registry=variables_registry) diff --git a/src/openforms/registrations/contrib/objects_api/registration_variables.py b/src/openforms/registrations/contrib/objects_api/registration_variables.py new file mode 100644 index 0000000000..e9b503d35e --- /dev/null +++ b/src/openforms/registrations/contrib/objects_api/registration_variables.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from django.utils.translation import gettext_lazy as _ + +from openforms.plugins.registry import BaseRegistry +from openforms.variables.base import BaseStaticVariable +from openforms.variables.constants import FormVariableDataTypes + +if TYPE_CHECKING: + from openforms.submissions.models import Submission + + +class Registry(BaseRegistry[BaseStaticVariable]): + """ + A registry for the Objects API registration variables. + """ + + module = "objects_api" + + +register = Registry() +"""The Objects API registration variables registry.""" + + +@register("pdf_url") +class PdfUrl(BaseStaticVariable): + name = _("PDF Url") + data_type = FormVariableDataTypes.string + + def get_initial_value(self, submission: Submission | None = None): + if submission is None: + return None + return submission.objects_api_registration_data.pdf_url + + +@register("csv_url") +class CsvUrl(BaseStaticVariable): + name = _("CSV Url") + data_type = FormVariableDataTypes.string + + def get_initial_value(self, submission: Submission | None = None): + if submission is None: + return None + return submission.objects_api_registration_data.csv_url + + +@register("attachment_urls") +class AttachmentUrls(BaseStaticVariable): + name = _("Attachment Urls") + data_type = FormVariableDataTypes.array + + def get_initial_value(self, submission: Submission | None = None): + if submission is None: + return None + return submission.objects_api_registration_data.attachment_urls + + +@register("payment_completed") +class PaymentCompleted(BaseStaticVariable): + name = _("Payment completed") + data_type = FormVariableDataTypes.boolean + + def get_initial_value(self, submission: Submission | None = None): + if submission is None: + return None + return submission.payment_user_has_paid + + +@register("payment_amount") +class PaymentAmount(BaseStaticVariable): + name = _("Payment amount") + data_type = FormVariableDataTypes.string + + def get_initial_value(self, submission: Submission | None = None): + if submission is None: + return None + return str(submission.payments.sum_amount()) + + +@register("payment_public_order_ids") +class PaymentPublicOrderIds(BaseStaticVariable): + name = _("Payment public order IDs") + data_type = FormVariableDataTypes.array + + def get_initial_value(self, submission: Submission | None = None): + if submission is None: + return None + return submission.payments.get_completed_public_order_ids() diff --git a/src/openforms/registrations/contrib/objects_api/submission_registration.py b/src/openforms/registrations/contrib/objects_api/submission_registration.py index 0e1868e5b9..59b350f418 100644 --- a/src/openforms/registrations/contrib/objects_api/submission_registration.py +++ b/src/openforms/registrations/contrib/objects_api/submission_registration.py @@ -1,4 +1,6 @@ -from typing import Any, Protocol, TypeVar, cast +from abc import ABC, abstractmethod +from contextlib import contextmanager +from typing import Any, Generic, Iterator, TypeVar, cast from typing_extensions import override @@ -9,6 +11,7 @@ create_csv_document, create_report_document, ) +from openforms.registrations.exceptions import RegistrationFailed from openforms.submissions.exports import create_submission_export from openforms.submissions.mapping import SKIP, FieldConf, apply_data_mapping from openforms.submissions.models import Submission, SubmissionReport @@ -16,27 +19,208 @@ from ...constants import REGISTRATION_ATTRIBUTE, RegistrationAttribute from .client import DocumentenClient, get_documents_client -from .typing import ConfigVersion, RegistrationOptionsV1, RegistrationOptionsV2 +from .models import ObjectsAPIRegistrationData +from .typing import ( + ConfigVersion, + RegistrationOptions, + RegistrationOptionsV1, + RegistrationOptionsV2, +) + + +def build_options( + plugin_options: RegistrationOptions, key_mapping: dict[str, str] +) -> dict[str, Any]: + """ + Construct options from plugin options dict, allowing renaming of keys + """ + options = { + new_key: plugin_options[key_in_opts] + for new_key, key_in_opts in key_mapping.items() + if key_in_opts in plugin_options + } + return options + + +def register_submission_pdf( + submission: Submission, + options: RegistrationOptions, + documents_client: DocumentenClient, +) -> str: + submission_report = SubmissionReport.objects.get(submission=submission) + submission_report_options = build_options( + options, + { + "informatieobjecttype": "informatieobjecttype_submission_report", + "organisatie_rsin": "organisatie_rsin", + "doc_vertrouwelijkheidaanduiding": "doc_vertrouwelijkheidaanduiding", + }, + ) + + report_document = create_report_document( + client=documents_client, + name=submission.form.admin_name, + submission_report=submission_report, + options=submission_report_options, + language=submission_report.submission.language_code, + ) + return report_document["url"] + + +def register_submission_csv( + submission: Submission, + options: RegistrationOptions, + documents_client: DocumentenClient, +) -> str: + if not options.get("upload_submission_csv", False): + return "" + + if not options["informatieobjecttype_submission_csv"]: + return "" + + submission_csv_options = build_options( + options, + { + "informatieobjecttype": "informatieobjecttype_submission_csv", + "organisatie_rsin": "organisatie_rsin", + "doc_vertrouwelijkheidaanduiding": "doc_vertrouwelijkheidaanduiding", + "auteur": "auteur", + }, + ) + qs = Submission.objects.filter(pk=submission.pk).select_related("auth_info") + submission_csv = create_submission_export(qs).export("csv") # type: ignore + + submission_csv_document = create_csv_document( + client=documents_client, + name=f"{submission.form.admin_name} (csv)", + csv_data=submission_csv, + options=submission_csv_options, + language=submission.language_code, + ) + + return submission_csv_document["url"] + + +def register_submission_attachments( + submission: Submission, + options: RegistrationOptions, + documents_client: DocumentenClient, +) -> list[str]: + attachment_urls: list[str] = [] + for attachment in submission.attachments: + attachment_options = build_options( + options, + { + "informatieobjecttype": "informatieobjecttype_attachment", # Different IOT than for the report + "organisatie_rsin": "organisatie_rsin", + "doc_vertrouwelijkheidaanduiding": "doc_vertrouwelijkheidaanduiding", + }, + ) + + component_overwrites = { + "doc_vertrouwelijkheidaanduiding": attachment.doc_vertrouwelijkheidaanduiding, + "titel": attachment.titel, + "organisatie_rsin": attachment.bronorganisatie, + "informatieobjecttype": attachment.informatieobjecttype, + } + + for key, value in component_overwrites.items(): + if value: + attachment_options[key] = value + + attachment_document = create_attachment_document( + client=documents_client, + name=submission.form.admin_name, + submission_attachment=attachment, + options=attachment_options, + language=attachment.submission_step.submission.language_code, # assume same as submission + ) + attachment_urls.append(attachment_document["url"]) + + return attachment_urls + + +@contextmanager +def save_and_raise(registration_data: ObjectsAPIRegistrationData) -> Iterator[None]: + """Save the registration data before raising a :class:`~openforms.registrations.exceptions.RegistrationFailed` exception.""" + + try: + yield + except Exception as e: + raise RegistrationFailed() from e + finally: + registration_data.save() + OptionsT = TypeVar( "OptionsT", RegistrationOptionsV1, RegistrationOptionsV2, contravariant=True ) -class ObjectsAPIRegistrationHandler(Protocol[OptionsT]): - """Provide the registration data to be sent to the Objects API.""" +class ObjectsAPIRegistrationHandler(ABC, Generic[OptionsT]): + """Provide the registration data to be sent to the Objects API. + + When registering a submission to the Objects API, the following happens: + - Depending on the version (v1 or v2) of the options, the correct handler is instantiated. + - ``save_registration_data`` is called, creating an instance of ``ObjectsAPIRegistrationData``. + - ``get_object_data`` is called, and should make use of the saved registration data to build the payload + to be sent to the Objects API. + - Similarly, ``get_update_payment_status_data`` is called to get the PATCH payload to be sent + to the Objects API. + """ + + def save_registration_data( + self, + submission: Submission, + options: OptionsT, + ) -> None: + """Save the registration data. + + The creation of submission documents (report, attachment, csv) makes use of ZGW + service functions (e.g. :func:`create_report_document`) and involves a mapping + (and in some cases renaming) of variables which would otherwise not be + accessible from here. For example, 'vertrouwelijkheidaanduiding' must be named + 'doc_vertrouwelijkheidaanduiding' because this is what the ZGW service functions + use. + """ + registration_data, _ = ObjectsAPIRegistrationData.objects.get_or_create( + submission=submission + ) + + with get_documents_client() as documents_client, save_and_raise( + registration_data + ): + if not registration_data.pdf_url: + registration_data.pdf_url = register_submission_pdf( + submission, options, documents_client + ) + + if not registration_data.csv_url: + registration_data.csv_url = register_submission_csv( + submission, options, documents_client + ) + + if not registration_data.attachment_urls: + # TODO turn attachments into a dictionary when giving users more options than + # just urls. + registration_data.attachment_urls = register_submission_attachments( + submission, options, documents_client + ) + @abstractmethod def get_object_data( self, submission: Submission, options: OptionsT, ) -> dict[str, Any]: """Get the object data payload to be sent to the Objects API.""" - ... + pass + @abstractmethod def get_update_payment_status_data( self, submission: Submission, options: OptionsT - ) -> dict[str, Any]: ... + ) -> dict[str, Any]: + pass class ObjectsAPIV1Handler(ObjectsAPIRegistrationHandler[RegistrationOptionsV1]): @@ -56,128 +240,17 @@ def get_payment_context_data(submission: Submission) -> dict[str, Any]: "public_order_ids": submission.payments.get_completed_public_order_ids(), } - @staticmethod - def build_options(plugin_options: RegistrationOptionsV1, key_mapping: dict) -> dict: - """ - Construct options from plugin options dict, allowing renaming of keys - """ - options = { - new_key: plugin_options[key_in_opts] - for new_key, key_in_opts in key_mapping.items() - if key_in_opts in plugin_options - } - return options - - def register_submission_csv( - self, - submission: Submission, - options: RegistrationOptionsV1, - documents_client: DocumentenClient, - ) -> str: - if not options.get("upload_submission_csv", False): - return "" - - if not options["informatieobjecttype_submission_csv"]: - return "" - - submission_csv_options = self.build_options( - options, - { - "informatieobjecttype": "informatieobjecttype_submission_csv", - "organisatie_rsin": "organisatie_rsin", - "doc_vertrouwelijkheidaanduiding": "doc_vertrouwelijkheidaanduiding", - "auteur": "auteur", - }, - ) - qs = Submission.objects.filter(pk=submission.pk).select_related("auth_info") - submission_csv = create_submission_export(qs).export("csv") # type: ignore - - submission_csv_document = create_csv_document( - client=documents_client, - name=f"{submission.form.admin_name} (csv)", - csv_data=submission_csv, - options=submission_csv_options, - language=submission.language_code, - ) - - return submission_csv_document["url"] - @override def get_object_data( self, submission: Submission, options: RegistrationOptionsV1, ) -> dict[str, Any]: - """Get the object data payload to be sent to the Objects API. - - The creation of submission documents (report, attachment, csv) makes use of ZGW - service functions (e.g. :func:`create_report_document`) and involves a mapping - (and in some cases renaming) of variables which would otherwise not be - accessible from here. For example, 'vertrouwelijkheidaanduiding' must be named - 'doc_vertrouwelijkheidaanduiding' because this is what the ZGW service functions - use. - """ - - # Prepare all documents to relate to the Objects API record - with get_documents_client() as documents_client: - # Create the document for the PDF summary - submission_report = SubmissionReport.objects.get(submission=submission) - submission_report_options = self.build_options( - options, - { - "informatieobjecttype": "informatieobjecttype_submission_report", - "organisatie_rsin": "organisatie_rsin", - "doc_vertrouwelijkheidaanduiding": "doc_vertrouwelijkheidaanduiding", - }, - ) - - report_document = create_report_document( - client=documents_client, - name=submission.form.admin_name, - submission_report=submission_report, - options=submission_report_options, - language=submission_report.submission.language_code, - ) - - # Register the attachments - # TODO turn attachments into dictionary when giving users more options then - # just urls. - attachment_urls: list[str] = [] - for attachment in submission.attachments: - attachment_options = self.build_options( - options, - { - "informatieobjecttype": "informatieobjecttype_attachment", # Different IOT than for the report - "organisatie_rsin": "organisatie_rsin", - "doc_vertrouwelijkheidaanduiding": "doc_vertrouwelijkheidaanduiding", - }, - ) - - component_overwrites = { - "doc_vertrouwelijkheidaanduiding": attachment.doc_vertrouwelijkheidaanduiding, - "titel": attachment.titel, - "organisatie_rsin": attachment.bronorganisatie, - "informatieobjecttype": attachment.informatieobjecttype, - } - - for key, value in component_overwrites.items(): - if value: - attachment_options[key] = value - - attachment_document = create_attachment_document( - client=documents_client, - name=submission.form.admin_name, - submission_attachment=attachment, - options=attachment_options, - language=attachment.submission_step.submission.language_code, # assume same as submission - ) - attachment_urls.append(attachment_document["url"]) + """Get the object data payload to be sent to the Objects API.""" - # Create the CSV submission export, if requested. - # If no CSV is being uploaded, then `assert csv_url == ""` applies. - csv_url = self.register_submission_csv( - submission, options, documents_client - ) + registration_data = ObjectsAPIRegistrationData.objects.get( + submission=submission + ) context = { "_submission": submission, @@ -190,9 +263,9 @@ def get_object_data( "public_reference": submission.public_registration_reference, "kenmerk": str(submission.uuid), "language_code": submission.language_code, - "uploaded_attachment_urls": attachment_urls, - "pdf_url": report_document["url"], - "csv_url": csv_url, + "uploaded_attachment_urls": registration_data.attachment_urls, + "pdf_url": registration_data.pdf_url, + "csv_url": registration_data.csv_url, }, } diff --git a/src/openforms/registrations/contrib/objects_api/tests/test_backend.py b/src/openforms/registrations/contrib/objects_api/tests/test_backend.py index 68b7069b39..0db4c9cd50 100644 --- a/src/openforms/registrations/contrib/objects_api/tests/test_backend.py +++ b/src/openforms/registrations/contrib/objects_api/tests/test_backend.py @@ -1,45 +1,29 @@ -import textwrap -from datetime import date from unittest.mock import patch -from django.test import TestCase, override_settings +from django.test import TestCase import requests_mock from zgw_consumers.constants import APITypes from zgw_consumers.test import generate_oas_component from zgw_consumers.test.factories import ServiceFactory -from openforms.payments.constants import PaymentStatus -from openforms.payments.tests.factories import SubmissionPaymentFactory -from openforms.submissions.tests.factories import ( - SubmissionFactory, - SubmissionFileAttachmentFactory, +from openforms.registrations.contrib.objects_api.models import ( + ObjectsAPIRegistrationData, ) +from openforms.registrations.exceptions import RegistrationFailed +from openforms.submissions.tests.factories import SubmissionFactory -from ....constants import RegistrationAttribute from ..models import ObjectsAPIConfig from ..plugin import PLUGIN_IDENTIFIER, ObjectsAPIRegistration -def get_create_json(req, ctx): - request_body = req.json() - return { - "url": "https://objecten.nl/api/v1/objects/1", - "uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", - "type": request_body["type"], - "record": { - "index": 0, - **request_body["record"], # typeVersion, data and startAt keys - "endAt": None, # see https://github.com/maykinmedia/objects-api/issues/349 - "registrationAt": date.today().isoformat(), - "correctionFor": 0, - "correctedBy": "", - }, - } - - @requests_mock.Mocker() class ObjectsAPIBackendTests(TestCase): + """General tests for the Objects API registration backend. + + These tests don't depend on the options version (v1 or v2). + """ + maxDiff = None def setUp(self): @@ -54,39 +38,6 @@ def setUp(self): api_root="https://documenten.nl/api/v1/", api_type=APITypes.drc, ), - objecttype="https://objecttypen.nl/api/v1/objecttypes/1", - objecttype_version=1, - productaanvraag_type="terugbelnotitie", - informatieobjecttype_submission_report="https://catalogi.nl/api/v1/informatieobjecttypen/1", - informatieobjecttype_submission_csv="https://catalogi.nl/api/v1/informatieobjecttypen/4", - informatieobjecttype_attachment="https://catalogi.nl/api/v1/informatieobjecttypen/3", - organisatie_rsin="000000000", - content_json=textwrap.dedent( - """ - { - "bron": { - "naam": "Open Formulieren", - "kenmerk": "{{ submission.kenmerk }}" - }, - "type": "{{ productaanvraag_type }}", - "aanvraaggegevens": {% json_summary %}, - "taal": "{{ submission.language_code }}", - "betrokkenen": [ - { - "inpBsn" : "{{ variables.auth_bsn }}", - "rolOmschrijvingGeneriek" : "initiator" - } - ], - "pdf": "{{ submission.pdf_url }}", - "csv": "{{ submission.csv_url }}", - "bijlagen": {% uploaded_attachment_urls %}, - "payment": { - "completed": {% if payment.completed %}true{% else %}false{% endif %}, - "amount": {{ payment.amount }}, - "public_order_ids": {{ payment.public_order_ids }} - } - }""" - ), ) config_patcher = patch( @@ -96,1160 +47,104 @@ def setUp(self): self.mock_get_config = config_patcher.start() self.addCleanup(config_patcher.stop) - def test_submission_with_objects_api_backend_override_defaults(self, m): - submission = SubmissionFactory.from_components( - [ - { - "key": "voornaam", - "type": "textfield", - "registration": { - "attribute": RegistrationAttribute.initiator_voornamen, - }, - }, - { - "key": "achternaam", - "type": "textfield", - "registration": { - "attribute": RegistrationAttribute.initiator_geslachtsnaam, - }, - }, - { - "key": "tussenvoegsel", - "type": "textfield", - "registration": { - "attribute": RegistrationAttribute.initiator_tussenvoegsel, - }, - }, - { - "key": "geboortedatum", - "type": "date", - "registration": { - "attribute": RegistrationAttribute.initiator_geboortedatum, - }, - }, - { - "key": "coordinaat", - "type": "map", - "registration": { - "attribute": RegistrationAttribute.locatie_coordinaat, - }, - }, - ], - submitted_data={ - "voornaam": "Foo", - "achternaam": "Bar", - "tussenvoegsel": "de", - "geboortedatum": "2000-12-31", - "coordinaat": [52.36673378967122, 4.893164274470299], - }, - language_code="en", - ) - submission_step = submission.steps[0] - assert submission_step.form_step - step_slug = submission_step.form_step.slug - - objects_form_options = dict( - objecttype="https://objecttypen.nl/api/v1/objecttypes/2", - objecttype_version=2, - productaanvraag_type="testproduct", - informatieobjecttype_submission_report="https://catalogi.nl/api/v1/informatieobjecttypen/2", - upload_submission_csv=True, - informatieobjecttype_submission_csv="https://catalogi.nl/api/v1/informatieobjecttypen/5", - organisatie_rsin="123456782", - zaak_vertrouwelijkheidaanduiding="geheim", - doc_vertrouwelijkheidaanduiding="geheim", - ) - - # Set up API mocks - expected_document_result = generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", - ) - expected_csv_document_result = generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", - ) - m.post( - "https://objecten.nl/api/v1/objects", - status_code=201, - json=get_create_json, - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=expected_document_result, - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=expected_csv_document_result, - additional_matcher=lambda req: "csv" in req.json()["bestandsnaam"], - ) - plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) - - # Run the registration - result = plugin.register_submission(submission, objects_form_options) - - # check the requests made - self.assertEqual(len(m.request_history), 3) - document_create, csv_document_create, object_create = m.request_history - - with self.subTest("object create call and registration result"): - submitted_object_data = object_create.json() - expected_object_body = { - "type": "https://objecttypen.nl/api/v1/objecttypes/2", - "record": { - "typeVersion": 2, - "data": { - "bron": { - "naam": "Open Formulieren", - "kenmerk": str(submission.uuid), - }, - "type": "testproduct", - "aanvraaggegevens": { - step_slug: { - "voornaam": "Foo", - "achternaam": "Bar", - "tussenvoegsel": "de", - "geboortedatum": "2000-12-31", - "coordinaat": [52.36673378967122, 4.893164274470299], - } - }, - "taal": "en", - "betrokkenen": [ - {"inpBsn": "", "rolOmschrijvingGeneriek": "initiator"} - ], - "pdf": expected_document_result["url"], - "csv": expected_csv_document_result["url"], - "bijlagen": [], - "payment": { - "completed": False, - "amount": 0, - "public_order_ids": [], - }, - }, - "startAt": date.today().isoformat(), - "geometry": { - "type": "Point", - "coordinates": [52.36673378967122, 4.893164274470299], - }, - }, - } - self.assertEqual(object_create.method, "POST") - self.assertEqual(object_create.url, "https://objecten.nl/api/v1/objects") - self.assertEqual(submitted_object_data, expected_object_body) - - # NOTE: the backend adds additional metadata that is not in the request body. - expected_result = { - "url": "https://objecten.nl/api/v1/objects/1", - "uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", - "type": objects_form_options["objecttype"], - "record": { - "index": 0, - "typeVersion": objects_form_options["objecttype_version"], - "data": submitted_object_data["record"]["data"], - "geometry": { - "type": "Point", - "coordinates": [52.36673378967122, 4.893164274470299], - }, - "startAt": date.today().isoformat(), - "endAt": None, - "registrationAt": date.today().isoformat(), - "correctionFor": 0, - "correctedBy": "", - }, - } - # Result is simply the created object - self.assertEqual(result, expected_result) - - with self.subTest("Document create (PDF summary)"): - document_create_body = document_create.json() - - self.assertEqual(document_create.method, "POST") - self.assertEqual( - document_create.url, - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - ) - self.assertEqual(document_create_body["bronorganisatie"], "123456782") - self.assertEqual( - document_create_body["informatieobjecttype"], - "https://catalogi.nl/api/v1/informatieobjecttypen/2", - ) - self.assertEqual( - document_create_body["vertrouwelijkheidaanduiding"], - "geheim", - ) - - with self.subTest("Document create (CSV export)"): - csv_document_create_body = csv_document_create.json() + def test_csv_creation_fails_pdf_still_saved(self, m: requests_mock.Mocker): + """Test the behavior when one of the API calls fails. - self.assertEqual(csv_document_create.method, "POST") - self.assertEqual( - csv_document_create.url, - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - ) - # Overridden informatieobjecttype used - self.assertEqual( - csv_document_create_body["informatieobjecttype"], - "https://catalogi.nl/api/v1/informatieobjecttypen/5", - ) + The exception should be caught, the intermediate data saved, and a + ``RegistrationFailed`` should be raised in the end. + """ - def test_submission_with_objects_api_backend_override_defaults_upload_csv_default_type( - self, m - ): submission = SubmissionFactory.from_components( [ { "key": "voornaam", - "registration": { - "attribute": RegistrationAttribute.initiator_voornamen, - }, + "type": "textfield", }, ], submitted_data={"voornaam": "Foo"}, ) - objects_form_options = dict( - objecttype="https://objecttypen.nl/api/v1/objecttypes/2", - objecttype_version=2, - productaanvraag_type="testproduct", - informatieobjecttype_submission_report="https://catalogi.nl/api/v1/informatieobjecttypen/2", - upload_submission_csv=True, - organisatie_rsin="123456782", - zaak_vertrouwelijkheidaanduiding="geheim", - doc_vertrouwelijkheidaanduiding="geheim", - ) - # Set up API mocks - expected_document_result = generate_oas_component( + pdf = generate_oas_component( "documenten", "schemas/EnkelvoudigInformatieObject", url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", ) - expected_csv_document_result = generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", - ) - m.post( - "https://objecten.nl/api/v1/objects", - status_code=201, - json=get_create_json, - ) + + # OK on PDF request m.post( "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", status_code=201, - json=expected_document_result, + json=pdf, + additional_matcher=lambda req: req.json()["bestandsnaam"].endswith(".pdf"), ) + + # Failure on CSV request (which is dispatched after the PDF one) m.post( "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=expected_csv_document_result, + status_code=500, additional_matcher=lambda req: "csv" in req.json()["bestandsnaam"], ) - plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) - - # Run the registration - plugin.register_submission(submission, objects_form_options) - - # check the requests made - self.assertEqual(len(m.request_history), 3) - document_create, csv_document_create, object_create = m.request_history - with self.subTest("object create call and registration result"): - submitted_object_data = object_create.json() - - self.assertEqual( - submitted_object_data["type"], - "https://objecttypen.nl/api/v1/objecttypes/2", - ) - self.assertEqual(submitted_object_data["record"]["typeVersion"], 2) - self.assertEqual( - submitted_object_data["record"]["data"]["type"], "testproduct" - ) - - with self.subTest("Document create (PDF summary)"): - document_create_body = document_create.json() - - self.assertEqual(document_create_body["bronorganisatie"], "123456782") - self.assertEqual( - document_create_body["informatieobjecttype"], - "https://catalogi.nl/api/v1/informatieobjecttypen/2", - ) - self.assertEqual( - document_create_body["vertrouwelijkheidaanduiding"], - "geheim", - ) - - with self.subTest("Document create (CSV export)"): - csv_document_create_body = csv_document_create.json() - - self.assertEqual( - csv_document_create.url, - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - ) - # Default informatieobjecttype used - self.assertEqual( - csv_document_create_body["informatieobjecttype"], - "https://catalogi.nl/api/v1/informatieobjecttypen/4", - ) + plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) - def test_submission_with_objects_api_backend_override_defaults_do_not_upload_csv( - self, m - ): - submission = SubmissionFactory.from_components( - [ + with self.assertRaises(RegistrationFailed): + plugin.register_submission( + submission, { - "key": "voornaam", - "registration": { - "attribute": RegistrationAttribute.initiator_voornamen, - }, + "upload_submission_csv": True, + "informatieobjecttype_submission_csv": "dummy", }, - ], - submitted_data={"voornaam": "Foo"}, - ) - # Set up API mocks - expected_document_result = generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", - ) - m.post( - "https://objecten.nl/api/v1/objects", - status_code=201, - json=get_create_json, - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=expected_document_result, - ) - plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) - - # Run the registration - plugin.register_submission(submission, {"upload_submission_csv": False}) - - # check the requests made - self.assertEqual(len(m.request_history), 2) - object_create = m.last_request - - with self.subTest("object create call and registration result"): - submitted_object_data = object_create.json() - - self.assertEqual(submitted_object_data["record"]["data"]["csv"], "") - self.assertEqual( - submitted_object_data["record"]["data"]["pdf"], - expected_document_result["url"], ) - def test_submission_with_objects_api_backend_missing_csv_iotype(self, m): - submission = SubmissionFactory.create(with_report=True, completed=True) - # Set up API mocks - expected_document_result = generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", - ) - m.post( - "https://objecten.nl/api/v1/objects", - status_code=201, - json=get_create_json, - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=expected_document_result, - ) - plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) - - # Run the registration - plugin.register_submission( - submission, - { - "upload_submission_csv": True, - "informatieobjecttype_submission_csv": "", - }, + registration_data = ObjectsAPIRegistrationData.objects.get( + submission=submission ) - # check the requests made - self.assertEqual(len(m.request_history), 2) - object_create = m.last_request + self.assertEqual(registration_data.pdf_url, pdf["url"]) + self.assertEqual(registration_data.csv_url, "") - with self.subTest("object create call and registration result"): - submitted_object_data = object_create.json() + def test_registration_works_after_failure(self, m: requests_mock.Mocker): + """Test the registration behavior after a failure. - self.assertEqual(submitted_object_data["record"]["data"]["csv"], "") - self.assertEqual( - submitted_object_data["record"]["data"]["pdf"], - expected_document_result["url"], - ) + As a ``ObjectsAPIRegistrationData`` instance was already created, it shouldn't crash. + """ - def test_submission_with_objects_api_backend_override_content_json(self, m): submission = SubmissionFactory.from_components( [ { "key": "voornaam", "type": "textfield", - "registration": { - "attribute": RegistrationAttribute.initiator_voornamen, - }, }, ], submitted_data={"voornaam": "Foo"}, - language_code="en", - ) - submission_step = submission.steps[0] - assert submission_step.form_step - step_slug = submission_step.form_step.slug - objects_form_options = dict( - upload_submission_csv=False, - content_json=textwrap.dedent( - """ - { - "bron": { - "naam": "Open Formulieren", - "kenmerk": "{{ submission.kenmerk }}" - }, - "type": "{{ productaanvraag_type }}", - "aanvraaggegevens": {% json_summary %}, - "taal": "{{ submission.language_code }}" - } - """ - ), ) - # Set up API mocks - expected_document_result = generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", - ) - m.post( - "https://objecten.nl/api/v1/objects", - status_code=201, - json=get_create_json, - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=expected_document_result, - ) - plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) - - # Run the registration - plugin.register_submission(submission, objects_form_options) - # check the requests made - self.assertEqual(len(m.request_history), 2) - - with self.subTest("object create call"): - object_create = m.last_request - expected_record_data = { - "bron": { - "naam": "Open Formulieren", - "kenmerk": str(submission.uuid), - }, - "type": "terugbelnotitie", - "aanvraaggegevens": {step_slug: {"voornaam": "Foo"}}, - "taal": "en", - } - - self.assertEqual(object_create.url, "https://objecten.nl/api/v1/objects") - object_create_body = object_create.json() - self.assertEqual(object_create_body["record"]["data"], expected_record_data) - - def test_submission_with_objects_api_backend_use_config_defaults(self, m): - submission = SubmissionFactory.from_components( - [ - { - "key": "voornaam", - "registration": { - "attribute": RegistrationAttribute.initiator_voornamen, - }, - } - ], - submitted_data={"voornaam": "Foo"}, - language_code="en", + # Instance created from a previous attempt + registration_data = ObjectsAPIRegistrationData.objects.create( + submission=submission, pdf_url="https://example.com" ) - submission_step = submission.steps[0] - assert submission_step.form_step - step_slug = submission_step.form_step.slug - # Set up API mocks - expected_document_result = generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", - ) - expected_csv_document_result = generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", - ) - m.post( - "https://objecten.nl/api/v1/objects", - status_code=201, - json=get_create_json, - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=expected_document_result, - ) + # Failure on CSV request (no mocks for the PDF one, we assume it was already created) m.post( "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=expected_csv_document_result, + status_code=500, additional_matcher=lambda req: "csv" in req.json()["bestandsnaam"], ) - plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) - - # Run the registration, applying default options from the config - plugin.register_submission(submission, {}) - - # check the requests made - self.assertEqual(len(m.request_history), 2) - document_create, object_create = m.request_history - - with self.subTest("Document create (PDF summary)"): - document_create_body = document_create.json() - - self.assertEqual( - document_create.url, - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - ) - self.assertEqual(document_create_body["taal"], "eng") - self.assertEqual(document_create_body["bronorganisatie"], "000000000") - self.assertEqual( - document_create_body["informatieobjecttype"], - "https://catalogi.nl/api/v1/informatieobjecttypen/1", - ) - self.assertNotIn("vertrouwelijkheidaanduiding", document_create_body) - - with self.subTest("object create call"): - object_create_body = object_create.json() - - expected_record_data = { - "typeVersion": 1, - "data": { - "aanvraaggegevens": {step_slug: {"voornaam": "Foo"}}, - "betrokkenen": [ - {"inpBsn": "", "rolOmschrijvingGeneriek": "initiator"} - ], - "bijlagen": [], - "bron": { - "kenmerk": str(submission.uuid), - "naam": "Open Formulieren", - }, - "csv": "", - "pdf": expected_document_result["url"], - "taal": "en", - "type": "terugbelnotitie", - "payment": { - "completed": False, - "amount": 0, - "public_order_ids": [], - }, - }, - "startAt": date.today().isoformat(), - } - self.assertEqual(object_create.url, "https://objecten.nl/api/v1/objects") - self.assertEqual(object_create_body["record"], expected_record_data) - def test_submission_with_objects_api_backend_attachments(self, m): - # Form.io configuration is irrelevant for this test, but normally you'd have - # set up some file upload components. - submission = SubmissionFactory.from_components( - [], - submitted_data={}, - language_code="en", - completed=True, - ) - submission_step = submission.steps[0] - # Set up two attachments to upload to the documents API - SubmissionFileAttachmentFactory.create( - submission_step=submission_step, file_name="attachment1.jpg" - ) - SubmissionFileAttachmentFactory.create( - submission_step=submission_step, file_name="attachment2.jpg" - ) - - # Set up API mocks - pdf, attachment1, attachment2 = [ - generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", - ), - generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", - ), - generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/3", - ), - ] - m.post( - "https://objecten.nl/api/v1/objects", - status_code=201, - json=get_create_json, - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=pdf, - additional_matcher=lambda req: req.json()["bestandsnaam"].endswith(".pdf"), - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=attachment1, - additional_matcher=lambda req: req.json()["bestandsnaam"] - == "attachment1.jpg", - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=attachment2, - additional_matcher=lambda req: req.json()["bestandsnaam"] - == "attachment2.jpg", - ) plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) - # Run the registration - plugin.register_submission(submission, {}) - - # check the requests made - self.assertEqual(len(m.request_history), 4) - ( - pdf_create, - attachment1_create, - attachment2_create, - object_create, - ) = m.request_history - - with self.subTest("object create call"): - record_data = object_create.json()["record"]["data"] - - self.assertEqual(object_create.url, "https://objecten.nl/api/v1/objects") - self.assertEqual( - record_data["pdf"], - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", - ) - self.assertEqual( - record_data["bijlagen"], - [ - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/3", - ], - ) - - with self.subTest("Document create (PDF summary)"): - pdf_create_data = pdf_create.json() - - self.assertEqual( - pdf_create.url, - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - ) - self.assertEqual(pdf_create_data["bronorganisatie"], "000000000") - self.assertEqual( - pdf_create_data["informatieobjecttype"], - "https://catalogi.nl/api/v1/informatieobjecttypen/1", - ) - self.assertNotIn("vertrouwelijkheidaanduiding", pdf_create_data) - - with self.subTest("Document create (attachment 1)"): - attachment1_create_data = attachment1_create.json() - - self.assertEqual( - attachment1_create.url, - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - ) - self.assertEqual(attachment1_create_data["bronorganisatie"], "000000000") - self.assertEqual(attachment1_create_data["taal"], "eng") - self.assertEqual( - attachment1_create_data["informatieobjecttype"], - "https://catalogi.nl/api/v1/informatieobjecttypen/3", - ) - self.assertNotIn("vertrouwelijkheidaanduiding", attachment1_create_data) - - with self.subTest("Document create (attachment 2)"): - attachment2_create_data = attachment2_create.json() - - self.assertEqual( - attachment1_create.url, - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - ) - self.assertEqual(attachment2_create_data["bronorganisatie"], "000000000") - self.assertEqual(attachment2_create_data["taal"], "eng") - self.assertEqual( - attachment2_create_data["informatieobjecttype"], - "https://catalogi.nl/api/v1/informatieobjecttypen/3", - ) - self.assertNotIn("vertrouwelijkheidaanduiding", attachment2_create_data) - - def test_submission_with_objects_api_backend_attachments_specific_iotypen(self, m): - submission = SubmissionFactory.from_components( - [ - { - "key": "field1", - "type": "file", - "registration": { - "informatieobjecttype": "https://catalogi.nl/api/v1/informatieobjecttypen/10", - }, - }, + with self.assertRaises(RegistrationFailed): + plugin.register_submission( + submission, { - "key": "field2", - "type": "file", - "registration": { - "informatieobjecttype": "", - }, + "upload_submission_csv": True, + "informatieobjecttype_submission_csv": "dummy", }, - ], - language_code="en", - ) - submission_step = submission.steps[0] - SubmissionFileAttachmentFactory.create( - submission_step=submission_step, - file_name="attachment1.jpg", - form_key="field1", - _component_configuration_path="components.0", - ) - SubmissionFileAttachmentFactory.create( - submission_step=submission_step, - file_name="attachment2.jpg", - form_key="field2", - _component_configuration_path="component.1", - ) - - # Set up API mocks - pdf, attachment1, attachment2 = [ - generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", - ), - generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", - ), - generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/3", - ), - ] - m.post( - "https://objecten.nl/api/v1/objects", - status_code=201, - json=get_create_json, - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=pdf, - additional_matcher=lambda req: req.json()["bestandsnaam"].endswith(".pdf"), - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=attachment1, - additional_matcher=lambda req: req.json()["bestandsnaam"] - == "attachment1.jpg", - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=attachment2, - additional_matcher=lambda req: req.json()["bestandsnaam"] - == "attachment2.jpg", - ) - plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) - - # Run the registration - plugin.register_submission(submission, {}) - - # check the requests made - self.assertEqual(len(m.request_history), 4) - attachment1_create = m.request_history[1] - attachment2_create = m.request_history[2] - - with self.subTest("Document create (attachment 1)"): - attachment1_create_data = attachment1_create.json() - - self.assertEqual( - attachment1_create.url, - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - ) - self.assertEqual(attachment1_create_data["bronorganisatie"], "000000000") - self.assertEqual(attachment1_create_data["taal"], "eng") - # Use override IOType - self.assertEqual( - attachment1_create_data["informatieobjecttype"], - "https://catalogi.nl/api/v1/informatieobjecttypen/10", - ) - self.assertNotIn("vertrouwelijkheidaanduiding", attachment1_create_data) - - with self.subTest("Document create (attachment 2)"): - attachment2_create_data = attachment2_create.json() - - self.assertEqual( - attachment1_create.url, - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", ) - self.assertEqual(attachment2_create_data["bronorganisatie"], "000000000") - self.assertEqual(attachment2_create_data["taal"], "eng") - # Fallback to default IOType - self.assertEqual( - attachment2_create_data["informatieobjecttype"], - "https://catalogi.nl/api/v1/informatieobjecttypen/3", - ) - self.assertNotIn("vertrouwelijkheidaanduiding", attachment2_create_data) - - def test_submission_with_objects_api_backend_attachments_component_overwrites( - self, m - ): - submission = SubmissionFactory.from_components( - [ - { - "key": "fileUpload", - "type": "file", - "registration": { - "informatieobjecttype": "https://catalogi.nl/api/v1/informatieobjecttypen/10", - "bronorganisatie": "123123123", - "docVertrouwelijkheidaanduiding": "geheim", - "titel": "A Custom Title", - }, - }, - ], - submitted_data={ - "fileUpload": [ - { - "url": "http://server/api/v2/submissions/files/62f2ec22-da7d-4385-b719-b8637c1cd483", - "data": { - "url": "http://server/api/v2/submissions/files/62f2ec22-da7d-4385-b719-b8637c1cd483", - "form": "", - "name": "some-attachment.jpg", - "size": 46114, - "baseUrl": "http://server/form", - "project": "", - }, - "name": "my-image-12305610-2da4-4694-a341-ccb919c3d543.jpg", - "size": 46114, - "type": "image/jpg", - "storage": "url", - "originalName": "some-attachment.jpg", - } - ], - }, - language_code="en", - ) - submission_step = submission.steps[0] - SubmissionFileAttachmentFactory.create( - submission_step=submission_step, - file_name="some-attachment.jpg", - form_key="fileUpload", - _component_configuration_path="components.0", - ) - - # Set up API mocks - pdf, attachment = [ - generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", - ), - generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", - ), - ] - m.post( - "https://objecten.nl/api/v1/objects", - status_code=201, - json=get_create_json, - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=pdf, - additional_matcher=lambda req: req.json()["bestandsnaam"].endswith(".pdf"), - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=attachment, - additional_matcher=lambda req: req.json()["bestandsnaam"] - == "some-attachment.jpg", - ) - plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) - # Run the registration - plugin.register_submission(submission, {}) - - # check the requests made - self.assertEqual(len(m.request_history), 3) - document_create_attachment = m.request_history[1] - - document_create_attachment_body = document_create_attachment.json() - self.assertEqual(document_create_attachment.method, "POST") - self.assertEqual( - document_create_attachment.url, - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + registration_data = ObjectsAPIRegistrationData.objects.get( + submission=submission ) - # Check use of override settings - self.assertEqual( - document_create_attachment_body["informatieobjecttype"], - "https://catalogi.nl/api/v1/informatieobjecttypen/10", - ) - self.assertEqual( - document_create_attachment_body["bronorganisatie"], "123123123" - ) - self.assertEqual( - document_create_attachment_body["vertrouwelijkheidaanduiding"], "geheim" - ) - self.assertEqual(document_create_attachment_body["titel"], "A Custom Title") - def test_submission_with_objects_api_backend_attachments_component_inside_fieldset_overwrites( - self, m - ): - submission = SubmissionFactory.from_components( - [ - { - "key": "fieldset", - "type": "fieldset", - "label": "A fieldset", - "components": [ - { - "key": "fileUpload", - "type": "file", - "registration": { - "informatieobjecttype": "https://catalogi.nl/api/v1/informatieobjecttypen/10", - "bronorganisatie": "123123123", - "docVertrouwelijkheidaanduiding": "geheim", - "titel": "A Custom Title", - }, - }, - ], - }, - ], - submitted_data={ - "fileUpload": [ - { - "url": "http://server/api/v2/submissions/files/62f2ec22-da7d-4385-b719-b8637c1cd483", - "data": { - "url": "http://server/api/v2/submissions/files/62f2ec22-da7d-4385-b719-b8637c1cd483", - "form": "", - "name": "some-attachment.jpg", - "size": 46114, - "baseUrl": "http://server/form", - "project": "", - }, - "name": "my-image-12305610-2da4-4694-a341-ccb919c3d543.jpg", - "size": 46114, - "type": "image/jpg", - "storage": "url", - "originalName": "some-attachment.jpg", - } - ], - }, - language_code="en", - ) - submission_step = submission.steps[0] - SubmissionFileAttachmentFactory.create( - submission_step=submission_step, - file_name="some-attachment.jpg", - form_key="fileUpload", - _component_configuration_path="components.0.components.0", - ) - # Set up API mocks - pdf, attachment = [ - generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", - ), - generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", - ), - ] - m.post( - "https://objecten.nl/api/v1/objects", - status_code=201, - json=get_create_json, - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=pdf, - additional_matcher=lambda req: req.json()["bestandsnaam"].endswith(".pdf"), - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=attachment, - additional_matcher=lambda req: req.json()["bestandsnaam"] - == "some-attachment.jpg", - ) - plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) - - # Run the registration - plugin.register_submission(submission, {}) - - # check the requests made - self.assertEqual(len(m.request_history), 3) - document_create_attachment = m.request_history[1] - - document_create_attachment_body = document_create_attachment.json() - self.assertEqual(document_create_attachment.method, "POST") - self.assertEqual( - document_create_attachment.url, - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - ) - # Check use of override settings - self.assertEqual( - document_create_attachment_body["informatieobjecttype"], - "https://catalogi.nl/api/v1/informatieobjecttypen/10", - ) - self.assertEqual( - document_create_attachment_body["bronorganisatie"], "123123123" - ) - self.assertEqual( - document_create_attachment_body["vertrouwelijkheidaanduiding"], "geheim" - ) - self.assertEqual(document_create_attachment_body["titel"], "A Custom Title") - - @override_settings(ESCAPE_REGISTRATION_OUTPUT=True) - def test_submission_with_objects_api_escapes_html(self, m): - content_template = textwrap.dedent( - """ - { - "summary": {% json_summary %}, - "manual_variable": "{{ variables.voornaam }}" - } - """ - ) - submission = SubmissionFactory.from_components( - [ - { - "key": "voornaam", - "type": "textfield", - "registration": { - "attribute": RegistrationAttribute.initiator_voornamen, - }, - }, - ], - submitted_data={"voornaam": ""}, - language_code="en", - ) - - submission_step = submission.steps[0] - assert submission_step.form_step - step_slug = submission_step.form_step.slug - # Set up API mocks - expected_document_result = generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", - ) - m.post( - "https://objecten.nl/api/v1/objects", - status_code=201, - json=get_create_json, - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=expected_document_result, - ) - plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) - - # Run the registration - plugin.register_submission( - submission, - { - "content_json": content_template, - "upload_submission_csv": False, - }, - ) - - self.assertEqual(len(m.request_history), 2) - - object_create = m.last_request - expected_record_data = { - "summary": { - step_slug: { - "voornaam": "<script>alert();</script>", - }, - }, - "manual_variable": "<script>alert();</script>", - } - object_create_body = object_create.json() - posted_record_data = object_create_body["record"]["data"] - self.assertEqual(object_create.method, "POST") - self.assertEqual(object_create.url, "https://objecten.nl/api/v1/objects") - self.assertEqual(posted_record_data, expected_record_data) - - def test_submission_with_payment(self, m): - submission = SubmissionFactory.from_components( - [ - { - "key": "test", - "type": "textfield", - }, - ], - registration_success=True, - submitted_data={"test": "some test data"}, - language_code="en", - registration_result={ - "url": "https://objecten.nl/api/v1/objects/111-222-333" - }, - ) - SubmissionPaymentFactory.create( - submission=submission, - status=PaymentStatus.started, - amount=10, - public_order_id="", - ) - - m.post( - "https://objecten.nl/api/v1/objects", - status_code=201, - json=get_create_json, - ) - m.post( - "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", - status_code=201, - json=generate_oas_component( - "documenten", - "schemas/EnkelvoudigInformatieObject", - url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", - ), - ) - - plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) - plugin.register_submission( - submission, - {}, - ) - - self.assertEqual(len(m.request_history), 2) - - object_create = m.last_request - body = object_create.json() - - self.assertEqual( - body["record"]["data"]["payment"], - { - "completed": False, - "amount": 10.00, - "public_order_ids": [], - }, - ) + self.assertEqual(registration_data.pdf_url, "https://example.com") + self.assertEqual(registration_data.csv_url, "") diff --git a/src/openforms/registrations/contrib/objects_api/tests/test_backend_v1.py b/src/openforms/registrations/contrib/objects_api/tests/test_backend_v1.py new file mode 100644 index 0000000000..266b1118e5 --- /dev/null +++ b/src/openforms/registrations/contrib/objects_api/tests/test_backend_v1.py @@ -0,0 +1,1255 @@ +import textwrap +from datetime import date +from unittest.mock import patch + +from django.test import TestCase, override_settings + +import requests_mock +from zgw_consumers.constants import APITypes +from zgw_consumers.test import generate_oas_component +from zgw_consumers.test.factories import ServiceFactory + +from openforms.payments.constants import PaymentStatus +from openforms.payments.tests.factories import SubmissionPaymentFactory +from openforms.submissions.tests.factories import ( + SubmissionFactory, + SubmissionFileAttachmentFactory, +) + +from ....constants import RegistrationAttribute +from ..models import ObjectsAPIConfig +from ..plugin import PLUGIN_IDENTIFIER, ObjectsAPIRegistration + + +def get_create_json(req, ctx): + request_body = req.json() + return { + "url": "https://objecten.nl/api/v1/objects/1", + "uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "type": request_body["type"], + "record": { + "index": 0, + **request_body["record"], # typeVersion, data and startAt keys + "endAt": None, # see https://github.com/maykinmedia/objects-api/issues/349 + "registrationAt": date.today().isoformat(), + "correctionFor": 0, + "correctedBy": "", + }, + } + + +@requests_mock.Mocker() +class ObjectsAPIBackendV1Tests(TestCase): + maxDiff = None + + def setUp(self): + super().setUp() + + config = ObjectsAPIConfig( + objects_service=ServiceFactory.build( + api_root="https://objecten.nl/api/v1/", + api_type=APITypes.orc, + ), + drc_service=ServiceFactory.build( + api_root="https://documenten.nl/api/v1/", + api_type=APITypes.drc, + ), + objecttype="https://objecttypen.nl/api/v1/objecttypes/1", + objecttype_version=1, + productaanvraag_type="terugbelnotitie", + informatieobjecttype_submission_report="https://catalogi.nl/api/v1/informatieobjecttypen/1", + informatieobjecttype_submission_csv="https://catalogi.nl/api/v1/informatieobjecttypen/4", + informatieobjecttype_attachment="https://catalogi.nl/api/v1/informatieobjecttypen/3", + organisatie_rsin="000000000", + content_json=textwrap.dedent( + """ + { + "bron": { + "naam": "Open Formulieren", + "kenmerk": "{{ submission.kenmerk }}" + }, + "type": "{{ productaanvraag_type }}", + "aanvraaggegevens": {% json_summary %}, + "taal": "{{ submission.language_code }}", + "betrokkenen": [ + { + "inpBsn" : "{{ variables.auth_bsn }}", + "rolOmschrijvingGeneriek" : "initiator" + } + ], + "pdf": "{{ submission.pdf_url }}", + "csv": "{{ submission.csv_url }}", + "bijlagen": {% uploaded_attachment_urls %}, + "payment": { + "completed": {% if payment.completed %}true{% else %}false{% endif %}, + "amount": {{ payment.amount }}, + "public_order_ids": {{ payment.public_order_ids }} + } + }""" + ), + ) + + config_patcher = patch( + "openforms.registrations.contrib.objects_api.models.ObjectsAPIConfig.get_solo", + return_value=config, + ) + self.mock_get_config = config_patcher.start() + self.addCleanup(config_patcher.stop) + + def test_submission_with_objects_api_backend_override_defaults(self, m): + submission = SubmissionFactory.from_components( + [ + { + "key": "voornaam", + "type": "textfield", + "registration": { + "attribute": RegistrationAttribute.initiator_voornamen, + }, + }, + { + "key": "achternaam", + "type": "textfield", + "registration": { + "attribute": RegistrationAttribute.initiator_geslachtsnaam, + }, + }, + { + "key": "tussenvoegsel", + "type": "textfield", + "registration": { + "attribute": RegistrationAttribute.initiator_tussenvoegsel, + }, + }, + { + "key": "geboortedatum", + "type": "date", + "registration": { + "attribute": RegistrationAttribute.initiator_geboortedatum, + }, + }, + { + "key": "coordinaat", + "type": "map", + "registration": { + "attribute": RegistrationAttribute.locatie_coordinaat, + }, + }, + ], + submitted_data={ + "voornaam": "Foo", + "achternaam": "Bar", + "tussenvoegsel": "de", + "geboortedatum": "2000-12-31", + "coordinaat": [52.36673378967122, 4.893164274470299], + }, + language_code="en", + ) + submission_step = submission.steps[0] + assert submission_step.form_step + step_slug = submission_step.form_step.slug + + objects_form_options = dict( + objecttype="https://objecttypen.nl/api/v1/objecttypes/2", + objecttype_version=2, + productaanvraag_type="testproduct", + informatieobjecttype_submission_report="https://catalogi.nl/api/v1/informatieobjecttypen/2", + upload_submission_csv=True, + informatieobjecttype_submission_csv="https://catalogi.nl/api/v1/informatieobjecttypen/5", + organisatie_rsin="123456782", + zaak_vertrouwelijkheidaanduiding="geheim", + doc_vertrouwelijkheidaanduiding="geheim", + ) + + # Set up API mocks + expected_document_result = generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", + ) + expected_csv_document_result = generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", + ) + m.post( + "https://objecten.nl/api/v1/objects", + status_code=201, + json=get_create_json, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=expected_document_result, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=expected_csv_document_result, + additional_matcher=lambda req: "csv" in req.json()["bestandsnaam"], + ) + plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) + + # Run the registration + result = plugin.register_submission(submission, objects_form_options) + + # check the requests made + self.assertEqual(len(m.request_history), 3) + document_create, csv_document_create, object_create = m.request_history + + with self.subTest("object create call and registration result"): + submitted_object_data = object_create.json() + expected_object_body = { + "type": "https://objecttypen.nl/api/v1/objecttypes/2", + "record": { + "typeVersion": 2, + "data": { + "bron": { + "naam": "Open Formulieren", + "kenmerk": str(submission.uuid), + }, + "type": "testproduct", + "aanvraaggegevens": { + step_slug: { + "voornaam": "Foo", + "achternaam": "Bar", + "tussenvoegsel": "de", + "geboortedatum": "2000-12-31", + "coordinaat": [52.36673378967122, 4.893164274470299], + } + }, + "taal": "en", + "betrokkenen": [ + {"inpBsn": "", "rolOmschrijvingGeneriek": "initiator"} + ], + "pdf": expected_document_result["url"], + "csv": expected_csv_document_result["url"], + "bijlagen": [], + "payment": { + "completed": False, + "amount": 0, + "public_order_ids": [], + }, + }, + "startAt": date.today().isoformat(), + "geometry": { + "type": "Point", + "coordinates": [52.36673378967122, 4.893164274470299], + }, + }, + } + self.assertEqual(object_create.method, "POST") + self.assertEqual(object_create.url, "https://objecten.nl/api/v1/objects") + self.assertEqual(submitted_object_data, expected_object_body) + + # NOTE: the backend adds additional metadata that is not in the request body. + expected_result = { + "url": "https://objecten.nl/api/v1/objects/1", + "uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "type": objects_form_options["objecttype"], + "record": { + "index": 0, + "typeVersion": objects_form_options["objecttype_version"], + "data": submitted_object_data["record"]["data"], + "geometry": { + "type": "Point", + "coordinates": [52.36673378967122, 4.893164274470299], + }, + "startAt": date.today().isoformat(), + "endAt": None, + "registrationAt": date.today().isoformat(), + "correctionFor": 0, + "correctedBy": "", + }, + } + # Result is simply the created object + self.assertEqual(result, expected_result) + + with self.subTest("Document create (PDF summary)"): + document_create_body = document_create.json() + + self.assertEqual(document_create.method, "POST") + self.assertEqual( + document_create.url, + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + ) + self.assertEqual(document_create_body["bronorganisatie"], "123456782") + self.assertEqual( + document_create_body["informatieobjecttype"], + "https://catalogi.nl/api/v1/informatieobjecttypen/2", + ) + self.assertEqual( + document_create_body["vertrouwelijkheidaanduiding"], + "geheim", + ) + + with self.subTest("Document create (CSV export)"): + csv_document_create_body = csv_document_create.json() + + self.assertEqual(csv_document_create.method, "POST") + self.assertEqual( + csv_document_create.url, + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + ) + # Overridden informatieobjecttype used + self.assertEqual( + csv_document_create_body["informatieobjecttype"], + "https://catalogi.nl/api/v1/informatieobjecttypen/5", + ) + + def test_submission_with_objects_api_backend_override_defaults_upload_csv_default_type( + self, m + ): + submission = SubmissionFactory.from_components( + [ + { + "key": "voornaam", + "registration": { + "attribute": RegistrationAttribute.initiator_voornamen, + }, + }, + ], + submitted_data={"voornaam": "Foo"}, + ) + objects_form_options = dict( + objecttype="https://objecttypen.nl/api/v1/objecttypes/2", + objecttype_version=2, + productaanvraag_type="testproduct", + informatieobjecttype_submission_report="https://catalogi.nl/api/v1/informatieobjecttypen/2", + upload_submission_csv=True, + organisatie_rsin="123456782", + zaak_vertrouwelijkheidaanduiding="geheim", + doc_vertrouwelijkheidaanduiding="geheim", + ) + + # Set up API mocks + expected_document_result = generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", + ) + expected_csv_document_result = generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", + ) + m.post( + "https://objecten.nl/api/v1/objects", + status_code=201, + json=get_create_json, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=expected_document_result, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=expected_csv_document_result, + additional_matcher=lambda req: "csv" in req.json()["bestandsnaam"], + ) + plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) + + # Run the registration + plugin.register_submission(submission, objects_form_options) + + # check the requests made + self.assertEqual(len(m.request_history), 3) + document_create, csv_document_create, object_create = m.request_history + + with self.subTest("object create call and registration result"): + submitted_object_data = object_create.json() + + self.assertEqual( + submitted_object_data["type"], + "https://objecttypen.nl/api/v1/objecttypes/2", + ) + self.assertEqual(submitted_object_data["record"]["typeVersion"], 2) + self.assertEqual( + submitted_object_data["record"]["data"]["type"], "testproduct" + ) + + with self.subTest("Document create (PDF summary)"): + document_create_body = document_create.json() + + self.assertEqual(document_create_body["bronorganisatie"], "123456782") + self.assertEqual( + document_create_body["informatieobjecttype"], + "https://catalogi.nl/api/v1/informatieobjecttypen/2", + ) + self.assertEqual( + document_create_body["vertrouwelijkheidaanduiding"], + "geheim", + ) + + with self.subTest("Document create (CSV export)"): + csv_document_create_body = csv_document_create.json() + + self.assertEqual( + csv_document_create.url, + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + ) + # Default informatieobjecttype used + self.assertEqual( + csv_document_create_body["informatieobjecttype"], + "https://catalogi.nl/api/v1/informatieobjecttypen/4", + ) + + def test_submission_with_objects_api_backend_override_defaults_do_not_upload_csv( + self, m + ): + submission = SubmissionFactory.from_components( + [ + { + "key": "voornaam", + "registration": { + "attribute": RegistrationAttribute.initiator_voornamen, + }, + }, + ], + submitted_data={"voornaam": "Foo"}, + ) + # Set up API mocks + expected_document_result = generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", + ) + m.post( + "https://objecten.nl/api/v1/objects", + status_code=201, + json=get_create_json, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=expected_document_result, + ) + plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) + + # Run the registration + plugin.register_submission(submission, {"upload_submission_csv": False}) + + # check the requests made + self.assertEqual(len(m.request_history), 2) + object_create = m.last_request + + with self.subTest("object create call and registration result"): + submitted_object_data = object_create.json() + + self.assertEqual(submitted_object_data["record"]["data"]["csv"], "") + self.assertEqual( + submitted_object_data["record"]["data"]["pdf"], + expected_document_result["url"], + ) + + def test_submission_with_objects_api_backend_missing_csv_iotype(self, m): + submission = SubmissionFactory.create(with_report=True, completed=True) + # Set up API mocks + expected_document_result = generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", + ) + m.post( + "https://objecten.nl/api/v1/objects", + status_code=201, + json=get_create_json, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=expected_document_result, + ) + plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) + + # Run the registration + plugin.register_submission( + submission, + { + "upload_submission_csv": True, + "informatieobjecttype_submission_csv": "", + }, + ) + + # check the requests made + self.assertEqual(len(m.request_history), 2) + object_create = m.last_request + + with self.subTest("object create call and registration result"): + submitted_object_data = object_create.json() + + self.assertEqual(submitted_object_data["record"]["data"]["csv"], "") + self.assertEqual( + submitted_object_data["record"]["data"]["pdf"], + expected_document_result["url"], + ) + + def test_submission_with_objects_api_backend_override_content_json(self, m): + submission = SubmissionFactory.from_components( + [ + { + "key": "voornaam", + "type": "textfield", + "registration": { + "attribute": RegistrationAttribute.initiator_voornamen, + }, + }, + ], + submitted_data={"voornaam": "Foo"}, + language_code="en", + ) + submission_step = submission.steps[0] + assert submission_step.form_step + step_slug = submission_step.form_step.slug + objects_form_options = dict( + upload_submission_csv=False, + content_json=textwrap.dedent( + """ + { + "bron": { + "naam": "Open Formulieren", + "kenmerk": "{{ submission.kenmerk }}" + }, + "type": "{{ productaanvraag_type }}", + "aanvraaggegevens": {% json_summary %}, + "taal": "{{ submission.language_code }}" + } + """ + ), + ) + # Set up API mocks + expected_document_result = generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", + ) + m.post( + "https://objecten.nl/api/v1/objects", + status_code=201, + json=get_create_json, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=expected_document_result, + ) + plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) + + # Run the registration + plugin.register_submission(submission, objects_form_options) + + # check the requests made + self.assertEqual(len(m.request_history), 2) + + with self.subTest("object create call"): + object_create = m.last_request + expected_record_data = { + "bron": { + "naam": "Open Formulieren", + "kenmerk": str(submission.uuid), + }, + "type": "terugbelnotitie", + "aanvraaggegevens": {step_slug: {"voornaam": "Foo"}}, + "taal": "en", + } + + self.assertEqual(object_create.url, "https://objecten.nl/api/v1/objects") + object_create_body = object_create.json() + self.assertEqual(object_create_body["record"]["data"], expected_record_data) + + def test_submission_with_objects_api_backend_use_config_defaults(self, m): + submission = SubmissionFactory.from_components( + [ + { + "key": "voornaam", + "registration": { + "attribute": RegistrationAttribute.initiator_voornamen, + }, + } + ], + submitted_data={"voornaam": "Foo"}, + language_code="en", + ) + submission_step = submission.steps[0] + assert submission_step.form_step + step_slug = submission_step.form_step.slug + + # Set up API mocks + expected_document_result = generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", + ) + expected_csv_document_result = generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", + ) + m.post( + "https://objecten.nl/api/v1/objects", + status_code=201, + json=get_create_json, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=expected_document_result, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=expected_csv_document_result, + additional_matcher=lambda req: "csv" in req.json()["bestandsnaam"], + ) + plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) + + # Run the registration, applying default options from the config + plugin.register_submission(submission, {}) + + # check the requests made + self.assertEqual(len(m.request_history), 2) + document_create, object_create = m.request_history + + with self.subTest("Document create (PDF summary)"): + document_create_body = document_create.json() + + self.assertEqual( + document_create.url, + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + ) + self.assertEqual(document_create_body["taal"], "eng") + self.assertEqual(document_create_body["bronorganisatie"], "000000000") + self.assertEqual( + document_create_body["informatieobjecttype"], + "https://catalogi.nl/api/v1/informatieobjecttypen/1", + ) + self.assertNotIn("vertrouwelijkheidaanduiding", document_create_body) + + with self.subTest("object create call"): + object_create_body = object_create.json() + + expected_record_data = { + "typeVersion": 1, + "data": { + "aanvraaggegevens": {step_slug: {"voornaam": "Foo"}}, + "betrokkenen": [ + {"inpBsn": "", "rolOmschrijvingGeneriek": "initiator"} + ], + "bijlagen": [], + "bron": { + "kenmerk": str(submission.uuid), + "naam": "Open Formulieren", + }, + "csv": "", + "pdf": expected_document_result["url"], + "taal": "en", + "type": "terugbelnotitie", + "payment": { + "completed": False, + "amount": 0, + "public_order_ids": [], + }, + }, + "startAt": date.today().isoformat(), + } + self.assertEqual(object_create.url, "https://objecten.nl/api/v1/objects") + self.assertEqual(object_create_body["record"], expected_record_data) + + def test_submission_with_objects_api_backend_attachments(self, m): + # Form.io configuration is irrelevant for this test, but normally you'd have + # set up some file upload components. + submission = SubmissionFactory.from_components( + [], + submitted_data={}, + language_code="en", + completed=True, + ) + submission_step = submission.steps[0] + # Set up two attachments to upload to the documents API + SubmissionFileAttachmentFactory.create( + submission_step=submission_step, file_name="attachment1.jpg" + ) + SubmissionFileAttachmentFactory.create( + submission_step=submission_step, file_name="attachment2.jpg" + ) + + # Set up API mocks + pdf, attachment1, attachment2 = [ + generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", + ), + generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", + ), + generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/3", + ), + ] + m.post( + "https://objecten.nl/api/v1/objects", + status_code=201, + json=get_create_json, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=pdf, + additional_matcher=lambda req: req.json()["bestandsnaam"].endswith(".pdf"), + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=attachment1, + additional_matcher=lambda req: req.json()["bestandsnaam"] + == "attachment1.jpg", + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=attachment2, + additional_matcher=lambda req: req.json()["bestandsnaam"] + == "attachment2.jpg", + ) + plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) + + # Run the registration + plugin.register_submission(submission, {}) + + # check the requests made + self.assertEqual(len(m.request_history), 4) + ( + pdf_create, + attachment1_create, + attachment2_create, + object_create, + ) = m.request_history + + with self.subTest("object create call"): + record_data = object_create.json()["record"]["data"] + + self.assertEqual(object_create.url, "https://objecten.nl/api/v1/objects") + self.assertEqual( + record_data["pdf"], + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", + ) + self.assertEqual( + record_data["bijlagen"], + [ + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/3", + ], + ) + + with self.subTest("Document create (PDF summary)"): + pdf_create_data = pdf_create.json() + + self.assertEqual( + pdf_create.url, + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + ) + self.assertEqual(pdf_create_data["bronorganisatie"], "000000000") + self.assertEqual( + pdf_create_data["informatieobjecttype"], + "https://catalogi.nl/api/v1/informatieobjecttypen/1", + ) + self.assertNotIn("vertrouwelijkheidaanduiding", pdf_create_data) + + with self.subTest("Document create (attachment 1)"): + attachment1_create_data = attachment1_create.json() + + self.assertEqual( + attachment1_create.url, + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + ) + self.assertEqual(attachment1_create_data["bronorganisatie"], "000000000") + self.assertEqual(attachment1_create_data["taal"], "eng") + self.assertEqual( + attachment1_create_data["informatieobjecttype"], + "https://catalogi.nl/api/v1/informatieobjecttypen/3", + ) + self.assertNotIn("vertrouwelijkheidaanduiding", attachment1_create_data) + + with self.subTest("Document create (attachment 2)"): + attachment2_create_data = attachment2_create.json() + + self.assertEqual( + attachment1_create.url, + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + ) + self.assertEqual(attachment2_create_data["bronorganisatie"], "000000000") + self.assertEqual(attachment2_create_data["taal"], "eng") + self.assertEqual( + attachment2_create_data["informatieobjecttype"], + "https://catalogi.nl/api/v1/informatieobjecttypen/3", + ) + self.assertNotIn("vertrouwelijkheidaanduiding", attachment2_create_data) + + def test_submission_with_objects_api_backend_attachments_specific_iotypen(self, m): + submission = SubmissionFactory.from_components( + [ + { + "key": "field1", + "type": "file", + "registration": { + "informatieobjecttype": "https://catalogi.nl/api/v1/informatieobjecttypen/10", + }, + }, + { + "key": "field2", + "type": "file", + "registration": { + "informatieobjecttype": "", + }, + }, + ], + language_code="en", + ) + submission_step = submission.steps[0] + SubmissionFileAttachmentFactory.create( + submission_step=submission_step, + file_name="attachment1.jpg", + form_key="field1", + _component_configuration_path="components.0", + ) + SubmissionFileAttachmentFactory.create( + submission_step=submission_step, + file_name="attachment2.jpg", + form_key="field2", + _component_configuration_path="component.1", + ) + + # Set up API mocks + pdf, attachment1, attachment2 = [ + generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", + ), + generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", + ), + generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/3", + ), + ] + m.post( + "https://objecten.nl/api/v1/objects", + status_code=201, + json=get_create_json, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=pdf, + additional_matcher=lambda req: req.json()["bestandsnaam"].endswith(".pdf"), + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=attachment1, + additional_matcher=lambda req: req.json()["bestandsnaam"] + == "attachment1.jpg", + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=attachment2, + additional_matcher=lambda req: req.json()["bestandsnaam"] + == "attachment2.jpg", + ) + plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) + + # Run the registration + plugin.register_submission(submission, {}) + + # check the requests made + self.assertEqual(len(m.request_history), 4) + attachment1_create = m.request_history[1] + attachment2_create = m.request_history[2] + + with self.subTest("Document create (attachment 1)"): + attachment1_create_data = attachment1_create.json() + + self.assertEqual( + attachment1_create.url, + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + ) + self.assertEqual(attachment1_create_data["bronorganisatie"], "000000000") + self.assertEqual(attachment1_create_data["taal"], "eng") + # Use override IOType + self.assertEqual( + attachment1_create_data["informatieobjecttype"], + "https://catalogi.nl/api/v1/informatieobjecttypen/10", + ) + self.assertNotIn("vertrouwelijkheidaanduiding", attachment1_create_data) + + with self.subTest("Document create (attachment 2)"): + attachment2_create_data = attachment2_create.json() + + self.assertEqual( + attachment1_create.url, + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + ) + self.assertEqual(attachment2_create_data["bronorganisatie"], "000000000") + self.assertEqual(attachment2_create_data["taal"], "eng") + # Fallback to default IOType + self.assertEqual( + attachment2_create_data["informatieobjecttype"], + "https://catalogi.nl/api/v1/informatieobjecttypen/3", + ) + self.assertNotIn("vertrouwelijkheidaanduiding", attachment2_create_data) + + def test_submission_with_objects_api_backend_attachments_component_overwrites( + self, m + ): + submission = SubmissionFactory.from_components( + [ + { + "key": "fileUpload", + "type": "file", + "registration": { + "informatieobjecttype": "https://catalogi.nl/api/v1/informatieobjecttypen/10", + "bronorganisatie": "123123123", + "docVertrouwelijkheidaanduiding": "geheim", + "titel": "A Custom Title", + }, + }, + ], + submitted_data={ + "fileUpload": [ + { + "url": "http://server/api/v2/submissions/files/62f2ec22-da7d-4385-b719-b8637c1cd483", + "data": { + "url": "http://server/api/v2/submissions/files/62f2ec22-da7d-4385-b719-b8637c1cd483", + "form": "", + "name": "some-attachment.jpg", + "size": 46114, + "baseUrl": "http://server/form", + "project": "", + }, + "name": "my-image-12305610-2da4-4694-a341-ccb919c3d543.jpg", + "size": 46114, + "type": "image/jpg", + "storage": "url", + "originalName": "some-attachment.jpg", + } + ], + }, + language_code="en", + ) + submission_step = submission.steps[0] + SubmissionFileAttachmentFactory.create( + submission_step=submission_step, + file_name="some-attachment.jpg", + form_key="fileUpload", + _component_configuration_path="components.0", + ) + + # Set up API mocks + pdf, attachment = [ + generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", + ), + generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", + ), + ] + m.post( + "https://objecten.nl/api/v1/objects", + status_code=201, + json=get_create_json, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=pdf, + additional_matcher=lambda req: req.json()["bestandsnaam"].endswith(".pdf"), + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=attachment, + additional_matcher=lambda req: req.json()["bestandsnaam"] + == "some-attachment.jpg", + ) + plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) + + # Run the registration + plugin.register_submission(submission, {}) + + # check the requests made + self.assertEqual(len(m.request_history), 3) + document_create_attachment = m.request_history[1] + + document_create_attachment_body = document_create_attachment.json() + self.assertEqual(document_create_attachment.method, "POST") + self.assertEqual( + document_create_attachment.url, + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + ) + # Check use of override settings + self.assertEqual( + document_create_attachment_body["informatieobjecttype"], + "https://catalogi.nl/api/v1/informatieobjecttypen/10", + ) + self.assertEqual( + document_create_attachment_body["bronorganisatie"], "123123123" + ) + self.assertEqual( + document_create_attachment_body["vertrouwelijkheidaanduiding"], "geheim" + ) + self.assertEqual(document_create_attachment_body["titel"], "A Custom Title") + + def test_submission_with_objects_api_backend_attachments_component_inside_fieldset_overwrites( + self, m + ): + submission = SubmissionFactory.from_components( + [ + { + "key": "fieldset", + "type": "fieldset", + "label": "A fieldset", + "components": [ + { + "key": "fileUpload", + "type": "file", + "registration": { + "informatieobjecttype": "https://catalogi.nl/api/v1/informatieobjecttypen/10", + "bronorganisatie": "123123123", + "docVertrouwelijkheidaanduiding": "geheim", + "titel": "A Custom Title", + }, + }, + ], + }, + ], + submitted_data={ + "fileUpload": [ + { + "url": "http://server/api/v2/submissions/files/62f2ec22-da7d-4385-b719-b8637c1cd483", + "data": { + "url": "http://server/api/v2/submissions/files/62f2ec22-da7d-4385-b719-b8637c1cd483", + "form": "", + "name": "some-attachment.jpg", + "size": 46114, + "baseUrl": "http://server/form", + "project": "", + }, + "name": "my-image-12305610-2da4-4694-a341-ccb919c3d543.jpg", + "size": 46114, + "type": "image/jpg", + "storage": "url", + "originalName": "some-attachment.jpg", + } + ], + }, + language_code="en", + ) + submission_step = submission.steps[0] + SubmissionFileAttachmentFactory.create( + submission_step=submission_step, + file_name="some-attachment.jpg", + form_key="fileUpload", + _component_configuration_path="components.0.components.0", + ) + # Set up API mocks + pdf, attachment = [ + generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", + ), + generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/2", + ), + ] + m.post( + "https://objecten.nl/api/v1/objects", + status_code=201, + json=get_create_json, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=pdf, + additional_matcher=lambda req: req.json()["bestandsnaam"].endswith(".pdf"), + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=attachment, + additional_matcher=lambda req: req.json()["bestandsnaam"] + == "some-attachment.jpg", + ) + plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) + + # Run the registration + plugin.register_submission(submission, {}) + + # check the requests made + self.assertEqual(len(m.request_history), 3) + document_create_attachment = m.request_history[1] + + document_create_attachment_body = document_create_attachment.json() + self.assertEqual(document_create_attachment.method, "POST") + self.assertEqual( + document_create_attachment.url, + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + ) + # Check use of override settings + self.assertEqual( + document_create_attachment_body["informatieobjecttype"], + "https://catalogi.nl/api/v1/informatieobjecttypen/10", + ) + self.assertEqual( + document_create_attachment_body["bronorganisatie"], "123123123" + ) + self.assertEqual( + document_create_attachment_body["vertrouwelijkheidaanduiding"], "geheim" + ) + self.assertEqual(document_create_attachment_body["titel"], "A Custom Title") + + @override_settings(ESCAPE_REGISTRATION_OUTPUT=True) + def test_submission_with_objects_api_escapes_html(self, m): + content_template = textwrap.dedent( + """ + { + "summary": {% json_summary %}, + "manual_variable": "{{ variables.voornaam }}" + } + """ + ) + submission = SubmissionFactory.from_components( + [ + { + "key": "voornaam", + "type": "textfield", + "registration": { + "attribute": RegistrationAttribute.initiator_voornamen, + }, + }, + ], + submitted_data={"voornaam": ""}, + language_code="en", + ) + + submission_step = submission.steps[0] + assert submission_step.form_step + step_slug = submission_step.form_step.slug + # Set up API mocks + expected_document_result = generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", + ) + m.post( + "https://objecten.nl/api/v1/objects", + status_code=201, + json=get_create_json, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=expected_document_result, + ) + plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) + + # Run the registration + plugin.register_submission( + submission, + { + "content_json": content_template, + "upload_submission_csv": False, + }, + ) + + self.assertEqual(len(m.request_history), 2) + + object_create = m.last_request + expected_record_data = { + "summary": { + step_slug: { + "voornaam": "<script>alert();</script>", + }, + }, + "manual_variable": "<script>alert();</script>", + } + object_create_body = object_create.json() + posted_record_data = object_create_body["record"]["data"] + self.assertEqual(object_create.method, "POST") + self.assertEqual(object_create.url, "https://objecten.nl/api/v1/objects") + self.assertEqual(posted_record_data, expected_record_data) + + def test_submission_with_payment(self, m): + submission = SubmissionFactory.from_components( + [ + { + "key": "test", + "type": "textfield", + }, + ], + registration_success=True, + submitted_data={"test": "some test data"}, + language_code="en", + registration_result={ + "url": "https://objecten.nl/api/v1/objects/111-222-333" + }, + ) + SubmissionPaymentFactory.create( + submission=submission, + status=PaymentStatus.started, + amount=10, + public_order_id="", + ) + + m.post( + "https://objecten.nl/api/v1/objects", + status_code=201, + json=get_create_json, + ) + m.post( + "https://documenten.nl/api/v1/enkelvoudiginformatieobjecten", + status_code=201, + json=generate_oas_component( + "documenten", + "schemas/EnkelvoudigInformatieObject", + url="https://documenten.nl/api/v1/enkelvoudiginformatieobjecten/1", + ), + ) + + plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) + plugin.register_submission( + submission, + {}, + ) + + self.assertEqual(len(m.request_history), 2) + + object_create = m.last_request + body = object_create.json() + + self.assertEqual( + body["record"]["data"]["payment"], + { + "completed": False, + "amount": 10.00, + "public_order_ids": [], + }, + ) diff --git a/src/openforms/typing.py b/src/openforms/typing.py index d3ea109569..ec45cd8708 100644 --- a/src/openforms/typing.py +++ b/src/openforms/typing.py @@ -1,7 +1,7 @@ import datetime import decimal import uuid -from typing import Any, NewType, Protocol, TypeAlias +from typing import TYPE_CHECKING, Any, NewType, Protocol, TypeAlias from django.http import HttpRequest from django.http.response import HttpResponseBase @@ -9,6 +9,11 @@ from rest_framework.request import Request +if TYPE_CHECKING: + from django.utils.functional import _StrOrPromise +else: + _StrOrPromise = str + JSONPrimitive: TypeAlias = str | int | float | bool | None JSONValue: TypeAlias = "JSONPrimitive | JSONObject | list[JSONValue]" @@ -21,6 +26,9 @@ RegistrationBackendKey = NewType("RegistrationBackendKey", str) +StrOrPromise: TypeAlias = _StrOrPromise +"""Either ``str`` or a ``Promise`` object returned by the lazy ``gettext`` functions.""" + class RequestHandler(Protocol): def __call__(self, request: HttpRequest) -> HttpResponseBase: ... diff --git a/src/openforms/variables/api/registration_serializer.py b/src/openforms/variables/api/registration_serializer.py new file mode 100644 index 0000000000..cb1bef3432 --- /dev/null +++ b/src/openforms/variables/api/registration_serializer.py @@ -0,0 +1,26 @@ +# The serializer is defined in a separate module to avoid circular imports. + +from django.utils.translation import gettext_lazy as _ + +from rest_framework import serializers + +from openforms.forms.api.serializers import FormVariableSerializer + + +class RegistrationPluginVariablesSerializer(serializers.Serializer): + plugin_identifier = serializers.CharField( + label=_("backend identifier"), + help_text=_("The identifier of the registration plugin."), + source="identifier", + ) + plugin_verbose_name = serializers.CharField( + label=_("backend verbose name"), + help_text=_("The verbose name of the registration plugin."), + source="verbose_name", + ) + plugin_variables = FormVariableSerializer( + many=True, + label=_("variables"), + help_text=_("The list of corresponding registration variables."), + source="get_variables", + ) diff --git a/src/openforms/variables/api/views.py b/src/openforms/variables/api/views.py index d6221e129a..61c538a7d2 100644 --- a/src/openforms/variables/api/views.py +++ b/src/openforms/variables/api/views.py @@ -2,19 +2,24 @@ from drf_spectacular.utils import extend_schema, extend_schema_view from rest_framework import authentication, permissions +from rest_framework.response import Response from rest_framework.views import APIView from openforms.api.serializers import ExceptionSerializer from openforms.api.views import ListMixin from openforms.forms.api.serializers import FormVariableSerializer +from openforms.registrations.registry import register as registration_plugins_registry from ..service import get_static_variables +from .registration_serializer import RegistrationPluginVariablesSerializer @extend_schema_view( get=extend_schema( - summary=_("Get static data"), - description=_("List the static data that will be associated with every form"), + summary=_("Get static variables"), + description=_( + "List the static variables that will be associated with every form" + ), responses={ 200: FormVariableSerializer(many=True), 403: ExceptionSerializer, @@ -28,3 +33,26 @@ class StaticFormVariablesView(ListMixin, APIView): def get_objects(self): return get_static_variables() + + +@extend_schema_view( + get=extend_schema( + summary=_("Get registration static variables"), + description=_( + "List the registration static variables that will be associated with every form" + ), + responses={ + 200: FormVariableSerializer(many=True), + 403: ExceptionSerializer, + }, + ), +) +class RegistrationPluginVariablesView(APIView): + authentication_classes = (authentication.SessionAuthentication,) + permission_classes = (permissions.IsAdminUser,) + + def get(self, request, *args, **kwargs): + serializer = RegistrationPluginVariablesSerializer( + registration_plugins_registry.iter_enabled_plugins(), many=True + ) + return Response(serializer.data) diff --git a/src/openforms/variables/base.py b/src/openforms/variables/base.py index cb5e1e0c95..3ff4ef83c6 100644 --- a/src/openforms/variables/base.py +++ b/src/openforms/variables/base.py @@ -1,16 +1,17 @@ from abc import ABC, abstractmethod -from dataclasses import dataclass, field +from typing import ClassVar from openforms.forms.models import FormVariable from openforms.plugins.plugin import AbstractBasePlugin from openforms.submissions.models import Submission +from openforms.typing import StrOrPromise + +from .constants import FormVariableDataTypes -@dataclass class BaseStaticVariable(ABC, AbstractBasePlugin): - identifier: str - name: str = field(init=False) - data_type: str = field(init=False) + name: ClassVar[StrOrPromise] + data_type: ClassVar[FormVariableDataTypes] @abstractmethod def get_initial_value(self, submission: Submission | None = None): diff --git a/src/openforms/variables/service.py b/src/openforms/variables/service.py index 50d0c6ea3f..bfc9ace622 100644 --- a/src/openforms/variables/service.py +++ b/src/openforms/variables/service.py @@ -3,14 +3,20 @@ """ from openforms.forms.models import FormVariable +from openforms.plugins.registry import BaseRegistry from openforms.submissions.models import Submission -from .registry import register_static_variable as register +from .base import BaseStaticVariable +from .registry import register_static_variable as static_variables_registry __all__ = ["get_static_variables"] -def get_static_variables(*, submission: Submission | None = None) -> list[FormVariable]: +def get_static_variables( + *, + submission: Submission | None = None, + variables_registry: BaseRegistry[BaseStaticVariable] | None = None, +) -> list[FormVariable]: """ Return the full collection of static variables registered by all apps. @@ -18,11 +24,15 @@ def get_static_variables(*, submission: Submission | None = None) -> list[FormVa variables for. Many of the static variables require the submission for sufficient context to be able to produce a value. Variables are static within that submission context, i.e. they don't change because of filling out the form. + :param variables_registry: The static variables registry to use. If not provided, + the default registry will be used. You should not rely on the order of returned variables, as they are registered in the order the Django apps are loaded - and this may change without notice. """ + if variables_registry is None: + variables_registry = static_variables_registry return [ registered_variable.get_static_variable(submission=submission) - for registered_variable in register + for registered_variable in variables_registry ] diff --git a/src/openforms/variables/tests/test_views.py b/src/openforms/variables/tests/test_views.py index 5ffcb298f6..3168aceb3f 100644 --- a/src/openforms/variables/tests/test_views.py +++ b/src/openforms/variables/tests/test_views.py @@ -6,10 +6,14 @@ from rest_framework.test import APITestCase from openforms.accounts.tests.factories import StaffUserFactory, UserFactory +from openforms.forms.models import FormVariable from openforms.prefill.constants import IdentifierRoles +from openforms.registrations.base import BasePlugin +from openforms.registrations.registry import Registry as RegistrationRegistry from openforms.variables.base import BaseStaticVariable from openforms.variables.constants import FormVariableDataTypes -from openforms.variables.registry import Registry +from openforms.variables.registry import Registry as VariableRegistry +from openforms.variables.service import get_static_variables class GetStaticVariablesViewTest(APITestCase): @@ -52,10 +56,12 @@ class DemoNow(BaseStaticVariable): def get_initial_value(self, *args, **kwargs): return "2021-07-16T21:15:00+00:00" - register = Registry() + register = VariableRegistry() register("now")(DemoNow) - with patch("openforms.variables.service.register", new=register): + with patch( + "openforms.variables.service.static_variables_registry", new=register + ): response = self.client.get(url) self.assertEqual(status.HTTP_200_OK, response.status_code) @@ -81,3 +87,94 @@ def get_initial_value(self, *args, **kwargs): }, data[0], ) + + +class GetRegistrationPluginVariablesViewTest(APITestCase): + def test_auth_required(self): + url = reverse( + "api:variables:registration", + ) + + response = self.client.get(url) + + self.assertEqual(status.HTTP_403_FORBIDDEN, response.status_code) + + def test_staff_required(self): + # add the permissions to verify we specifically check is_staff + user = UserFactory.create( + is_staff=False, user_permissions=["view_formvariable"] + ) + url = reverse( + "api:variables:registration", + ) + + self.client.force_authenticate(user=user) + response = self.client.get(url) + + self.assertEqual(status.HTTP_403_FORBIDDEN, response.status_code) + + def test_get_registration_plugin_variable(self): + user = StaffUserFactory.create(user_permissions=["change_form"]) + url = reverse( + "api:variables:registration", + ) + + self.client.force_authenticate(user=user) + + class DemoNow(BaseStaticVariable): + name = "Now" + data_type = FormVariableDataTypes.string + + def get_initial_value(self, *args, **kwargs): + return "demo initial value" + + variables_registry = VariableRegistry() + variables_registry("now")(DemoNow) + + class DemoRegistrationPlugin(BasePlugin): + verbose_name = "Demo verbose name" + + def register_submission(self, submission, options): + pass + + def get_variables(self) -> list[FormVariable]: + return get_static_variables(variables_registry=variables_registry) + + plugin_registry = RegistrationRegistry() + plugin_registry("demo")(DemoRegistrationPlugin) + + with patch( + "openforms.variables.api.views.registration_plugins_registry", + new=plugin_registry, + ): + response = self.client.get(url) + + self.assertEqual(status.HTTP_200_OK, response.status_code) + + data = response.data + + self.assertEqual(1, len(data)) + self.assertEqual( + { + "plugin_identifier": "demo", + "plugin_verbose_name": "Demo verbose name", + "plugin_variables": [ + { + "form": None, + "form_definition": None, + "name": "Now", + "key": "now", + "source": "", + "service_fetch_configuration": None, + "prefill_plugin": "", + "prefill_attribute": "", + "prefill_identifier_role": IdentifierRoles.main, + "data_type": FormVariableDataTypes.string, + "data_format": "", + "is_sensitive_data": False, + "initial_value": "demo initial value", + } + ], + }, + data[0], + ) diff --git a/src/openforms/variables/urls.py b/src/openforms/variables/urls.py index 45c9c67c84..7161165c82 100644 --- a/src/openforms/variables/urls.py +++ b/src/openforms/variables/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from .api.views import StaticFormVariablesView +from .api.views import RegistrationPluginVariablesView, StaticFormVariablesView app_name = "variables" @@ -9,5 +9,10 @@ "static", StaticFormVariablesView.as_view(), name="static", - ) + ), + path( + "registration", + RegistrationPluginVariablesView.as_view(), + name="registration", + ), ]