diff --git a/CHANGELOG.md b/CHANGELOG.md index dbf85d8ee9..d9467eb66c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to ### Added -- Signature backend can retrieve signing procedure to see its progress +- Signature backend can now retrieve the signing state of a document - Send an email to the user when an installment debit has been refused - Send an email to the user when an installment is successfully diff --git a/src/backend/joanie/core/utils/signature.py b/src/backend/joanie/core/utils/signature.py index dba553fd47..0bb2d349c6 100644 --- a/src/backend/joanie/core/utils/signature.py +++ b/src/backend/joanie/core/utils/signature.py @@ -33,3 +33,15 @@ def check_signature(request, settings_name): ) if not signature_is_valid: raise exceptions.AuthenticationFailed("Invalid authentication.") + + +def get_signature_progress_state(value: int) -> dict: + """ + Return a dictionary with boolean values whether the student and the organization have signed, + or nobody did. + The `value` represents the progress: + - 0: Neither the student nor the organization has signed. + - 50: The student has signed, but the organization has not. + - 100: Both the student and the organization have signed. + """ + return {"student": value >= 50, "organization": value == 100} # noqa: PLR2004 diff --git a/src/backend/joanie/signature/backends/base.py b/src/backend/joanie/signature/backends/base.py index fc42a31c13..5c646521e9 100644 --- a/src/backend/joanie/signature/backends/base.py +++ b/src/backend/joanie/signature/backends/base.py @@ -152,11 +152,11 @@ def update_signatories(self, reference_id: str, all_signatories: bool): "update_signatories() method." ) - def get_signature_procedure_progress(self, reference_id: str): + def get_signature_state(self, reference_id: str): """ - Get the signature procedure progress on a given reference id of a contract. + Get the signature state of a contract to know who has signed yet the document. """ raise NotImplementedError( "subclasses of BaseSignatureBackend must provide a " - "get_signature_procedure_progress() method." + "get_signature_state() method." ) diff --git a/src/backend/joanie/signature/backends/dummy.py b/src/backend/joanie/signature/backends/dummy.py index b023b80ccc..6385909f0d 100644 --- a/src/backend/joanie/signature/backends/dummy.py +++ b/src/backend/joanie/signature/backends/dummy.py @@ -148,18 +148,13 @@ def update_signatories(self, reference_id: str, all_signatories: bool) -> str: return contract.signature_backend_reference - def get_signature_procedure_progress(self, reference_id: str) -> int: + def get_signature_state(self, reference_id: str) -> int: """ - Dummy method that returns the progress of the signature on a contract. - Returns an integer value representing the progress of the signature process: - - 0: No one has signed - - 50: One person has signed - - 100: All required signatories have signed + Dummy method that returns the state of document in signing process. + It returns whether the student and the organization have signed. """ if not reference_id.startswith(self.prefix_workflow): - raise ValidationError( - f"Cannot get progress of contract with reference id : {reference_id}." - ) + raise ValidationError(f"The reference does not exist: {reference_id}.") try: contract = Contract.objects.get(signature_backend_reference=reference_id) except Contract.DoesNotExist as exception: @@ -167,8 +162,7 @@ def get_signature_procedure_progress(self, reference_id: str) -> int: f"Contract with reference id {reference_id} does not exist." ) from exception - if contract.student_signed_on and contract.organization_signed_on: - return 100 - if contract.student_signed_on: - return 50 - return 0 + return { + "student": bool(contract.student_signed_on), + "organization": bool(contract.organization_signed_on), + } diff --git a/src/backend/joanie/signature/backends/lex_persona.py b/src/backend/joanie/signature/backends/lex_persona.py index bcc34ed4c1..b2b1e9764a 100644 --- a/src/backend/joanie/signature/backends/lex_persona.py +++ b/src/backend/joanie/signature/backends/lex_persona.py @@ -13,6 +13,7 @@ from joanie.core import enums, models from joanie.core.utils.contract import order_has_organization_owner +from joanie.core.utils.signature import get_signature_progress_state from joanie.signature import exceptions from joanie.signature.backends.base import BaseSignatureBackend @@ -662,14 +663,10 @@ def update_signatories(self, reference_id: str, all_signatories: bool) -> str: return response.json()["id"] - def get_signature_procedure_progress(self, reference_id: str) -> int: + def get_signature_state(self, reference_id: str) -> dict: """ - Get the signature procedure progress with the reference. - This method allows us to get the information of how far the signing process has gone - since its creation. When there are 2 required signatories, the value : - - 0 will mean that nobody has signed - - 50 will mean that one person has signed - - 100 will mean that all required signatories have signed. + Get the signature state progress on a signing procedure. + It returns a dictionary whether the student and the organization have signed. """ timeout = settings.JOANIE_SIGNATURE_TIMEOUT base_url = self.get_setting("BASE_URL") @@ -695,4 +692,4 @@ def get_signature_procedure_progress(self, reference_id: str) -> int: f" the reference does not exist {reference_id}" ) - return response.json().get("progress") + return get_signature_progress_state(response.json().get("progress")) diff --git a/src/backend/joanie/tests/signature/backends/lex_persona/test_get_signature_procedure_progress.py b/src/backend/joanie/tests/signature/backends/lex_persona/test_get_signature_state.py similarity index 89% rename from src/backend/joanie/tests/signature/backends/lex_persona/test_get_signature_procedure_progress.py rename to src/backend/joanie/tests/signature/backends/lex_persona/test_get_signature_state.py index 2e619d6691..bd7c75a724 100644 --- a/src/backend/joanie/tests/signature/backends/lex_persona/test_get_signature_procedure_progress.py +++ b/src/backend/joanie/tests/signature/backends/lex_persona/test_get_signature_state.py @@ -1,4 +1,4 @@ -"""Test suite for the Lex Persona Signature Backend `get_signature_procedure_progress`""" +"""Test suite for the Lex Persona Signature Backend `get_signature_state`""" from http import HTTPStatus @@ -21,23 +21,23 @@ JOANIE_SIGNATURE_VALIDITY_PERIOD_IN_SECONDS=60 * 60 * 24 * 15, JOANIE_SIGNATURE_TIMEOUT=3, ) -class LexPersonaBackendGetSignatureProcedureProgress(TestCase): +class LexPersonaBackendGetSignatureState(TestCase): """ - Test suite for `get_signature_procedure_progress` + Test suite for `get_signature_state` """ @responses.activate - def test_backend_lex_persona_get_signature_procedure_progress_nobody_has_signed_yet( + def test_backend_lex_persona_get_signature_state_when_nobody_has_signed_yet( self, ): """ - Test that the method `get_signature_procedure_progress` will return the value 0 - because nobody signed the contract. + Test that the method `get_signature_state` return that nobody has signed the document. + It should return the value False for the student and the organization in the dictionnary. """ backend = get_signature_backend() workflow_id = "wfl_fake_id" api_url = f"https://lex_persona.test01.com/api/workflows/{workflow_id}/" - expected_response = { + response = { "allowConsolidation": True, "allowedCoManagerUsers": [], "coManagerNotifiedEvents": [], @@ -130,27 +130,28 @@ def test_backend_lex_persona_get_signature_procedure_progress_nobody_has_signed_ responses.add( responses.GET, api_url, - json=expected_response, + json=response, status=HTTPStatus.OK, ) - progress = backend.get_signature_procedure_progress(reference_id=workflow_id) + signature_state = backend.get_signature_state(reference_id=workflow_id) - self.assertEqual(progress, 0) + self.assertEqual(signature_state, {"student": False, "organization": False}) @responses.activate - def test_backend_lex_persona_get_signature_procedure_progress_one_person_signed( + def test_backend_lex_persona_get_signature_state_when_one_person_has_signed( self, ): """ - Test that the method `get_signature_procedure_progress` will return the value 50 - because 1 person has signed the contract. + Test that the method `get_signature_state` that the student has signed the document. + It should return the value True for the student and False for the organization + in the dictionary. """ backend = get_signature_backend() workflow_id = "wfl_fake_id" api_url = f"https://lex_persona.test01.com/api/workflows/{workflow_id}/" - expected_response = { + response = { "allowConsolidation": True, "allowedCoManagerUsers": [], "coManagerNotifiedEvents": [], @@ -255,26 +256,26 @@ def test_backend_lex_persona_get_signature_procedure_progress_one_person_signed( responses.add( responses.GET, api_url, - json=expected_response, + json=response, status=HTTPStatus.OK, ) - progress = backend.get_signature_procedure_progress(reference_id=workflow_id) + signature_state = backend.get_signature_state(reference_id=workflow_id) - self.assertEqual(progress, 50) + self.assertEqual(signature_state, {"student": True, "organization": False}) @responses.activate - def test_backend_lex_persona_get_signature_procedure_progress_all_signatories_signed( + def test_backend_lex_persona_get_signature_state_all_signatories_have_signed( self, ): """ - Test that the method `get_signature_procedure_progress` will return the value 100 - because all signatories have signed the contract. + Test that the method `get_signature_state` that both have signed the document. + It should return the value True for the student and the organization in the dictionary. """ backend = get_signature_backend() workflow_id = "wfl_fake_id" api_url = f"https://lex_persona.test01.com/api/workflows/{workflow_id}/" - expected_response = { + response = { "allowConsolidation": True, "allowedCoManagerUsers": [], "coManagerNotifiedEvents": [], @@ -396,26 +397,26 @@ def test_backend_lex_persona_get_signature_procedure_progress_all_signatories_si responses.add( responses.GET, api_url, - json=expected_response, + json=response, status=HTTPStatus.OK, ) - progress = backend.get_signature_procedure_progress(reference_id=workflow_id) + signature_state = backend.get_signature_state(reference_id=workflow_id) - self.assertEqual(progress, 100) + self.assertEqual(signature_state, {"student": True, "organization": True}) @responses.activate - def test_backend_lex_persona_get_signature_procedure_progress_returns_not_found( + def test_backend_lex_persona_get_signature_state_returns_not_found( self, ): """ - Test that the method `get_signature_procedure_progress` should return a status code + Test that the method `get_signature_state` should return a status code NOT_FOUND (404) because the reference does not exist at the signature provider. """ backend = get_signature_backend() workflow_id = "wfl_fake_id_not_exist" api_url = f"https://lex_persona.test01.com/api/workflows/{workflow_id}/" - expected_failing_response = { + response = { "status": 404, "error": "Not Found", "message": "The specified workflow can not be found.", @@ -426,12 +427,12 @@ def test_backend_lex_persona_get_signature_procedure_progress_returns_not_found( responses.add( responses.GET, api_url, - json=expected_failing_response, + json=response, status=HTTPStatus.NOT_FOUND, ) with self.assertRaises(exceptions.SignatureProcedureNotFound) as context: - backend.get_signature_procedure_progress(reference_id=workflow_id) + backend.get_signature_state(reference_id=workflow_id) self.assertEqual( str(context.exception), diff --git a/src/backend/joanie/tests/signature/test_backend_signature_base.py b/src/backend/joanie/tests/signature/test_backend_signature_base.py index 5beb68196f..7675762218 100644 --- a/src/backend/joanie/tests/signature/test_backend_signature_base.py +++ b/src/backend/joanie/tests/signature/test_backend_signature_base.py @@ -171,20 +171,20 @@ def test_backend_signature_base_backend_reset_contract(self): @override_settings( JOANIE_SIGNATURE_BACKEND="joanie.signature.backends.base.BaseSignatureBackend", ) - def test_backend_signature_base_raise_not_implemented_error_get_signature_procedure_progress( + def test_backend_signature_base_raise_not_implemented_error_get_signature_state( self, ): """ Base backend signature provider should raise NotImplementedError for the method - `get_signature_procedure_progress`. + `get_signature_state`. """ backend = get_signature_backend() with self.assertRaises(NotImplementedError) as context: - backend.get_signature_procedure_progress(reference_id="123") + backend.get_signature_state(reference_id="123") self.assertEqual( str(context.exception), "subclasses of BaseSignatureBackend must provide a " - "get_signature_procedure_progress() method.", + "get_signature_state() method.", ) diff --git a/src/backend/joanie/tests/signature/test_backend_signature_dummy.py b/src/backend/joanie/tests/signature/test_backend_signature_dummy.py index 6471acde58..a72942c023 100644 --- a/src/backend/joanie/tests/signature/test_backend_signature_dummy.py +++ b/src/backend/joanie/tests/signature/test_backend_signature_dummy.py @@ -497,12 +497,11 @@ def test_backend_dummy_update_organization_signatories_order_without_contract(se "The reference fake_signature_reference does not exist.", ) - def test_backend_dummy_get_signature_procedure_progress(self): + def test_backend_dummy_get_signature_state(self): """ Dummy backend instance should return the value of how many people have signed the - document. When the value is 0 it means that nobody has signed the document, if the - value is 50 it means that one person signed, and 100 means that all required signotories - have signed. + document. It returns a dictionary with boolean value that gives us the information + if the student has signed and the organization. """ backend = DummySignatureBackend() order = factories.OrderFactory( @@ -520,65 +519,61 @@ def test_backend_dummy_get_signature_procedure_progress(self): organization_signed_on=None, ) - progress = backend.get_signature_procedure_progress( + signature_state = backend.get_signature_state( contract.signature_backend_reference ) - self.assertEqual(progress, 0) + self.assertEqual(signature_state, {"student": False, "organization": False}) contract.student_signed_on = django_timezone.now() contract.save() - progress = backend.get_signature_procedure_progress( + signature_state = backend.get_signature_state( contract.signature_backend_reference ) - self.assertEqual(progress, 50) + self.assertEqual(signature_state, {"student": True, "organization": False}) contract.organization_signed_on = django_timezone.now() contract.submitted_for_signature_on = None contract.save() - progress = backend.get_signature_procedure_progress( + signature_state = backend.get_signature_state( contract.signature_backend_reference ) - self.assertEqual(progress, 100) + self.assertEqual(signature_state, {"student": True, "organization": True}) - def test_backend_dummy_get_signature_procedure_progress_with_non_existing_reference_id( + def test_backend_dummy_get_signature_state_with_non_existing_reference_id( self, ): """ - Dummy backend instance should not update a signature procedure if the contract is - fully signed. + Dummy backend instance should not return a dictionary if the passed `reference_id` + is not attached to any contract and raise a `ValidationError`. """ backend = DummySignatureBackend() with self.assertRaises(ValidationError) as context: - backend.get_signature_procedure_progress( - reference_id="wfl_fake_dummy_id_does_not_exist" - ) + backend.get_signature_state(reference_id="wfl_fake_dummy_id_does_not_exist") self.assertEqual( str(context.exception.message), "Contract with reference id wfl_fake_dummy_id_does_not_exist does not exist.", ) - def test_backend_dummy_get_signature_procedure_progress_with_wrong_format_reference_id( + def test_backend_dummy_get_signature_state_with_wrong_format_reference_id( self, ): """ - Dummy backend instance should not update a signature procedure if the contract is - fully signed. + Dummy backend instance should raise a `ValidationError` if the reference_id + has the wrong format for the Dummy Backend. """ backend = DummySignatureBackend() with self.assertRaises(ValidationError) as context: - backend.get_signature_procedure_progress( - reference_id="fake_dummy_id_does_not_exist" - ) + backend.get_signature_state(reference_id="fake_dummy_id_does_not_exist") self.assertEqual( str(context.exception.message), - "Cannot get progress of contract with reference id : fake_dummy_id_does_not_exist.", + "The reference does not exist: fake_dummy_id_does_not_exist.", )