esup_signature: add new-with-workflow endpoint (#77670) #251
|
@ -35,7 +35,7 @@ SIGN_REQUEST_SCHEMA = {
|
|||
'title': '',
|
||||
'description': '',
|
||||
'type': 'object',
|
||||
'required': ['file', 'recipients_emails', 'eppn'],
|
||||
'required': ['file', 'recipients_emails', 'create_by_eppn'],
|
||||
'unflatten': True,
|
||||
'properties': collections.OrderedDict(
|
||||
{
|
||||
|
@ -62,13 +62,120 @@ SIGN_REQUEST_SCHEMA = {
|
|||
'description': 'Recipients emails',
|
||||
'items': {'type': 'string'},
|
||||
},
|
||||
'eppn': {'type': 'string', 'description': 'EPPN of the sign request owner'},
|
||||
'recipients_cc_emails': {
|
||||
'type': 'array',
|
||||
'description': 'Recipients CC emails',
|
||||
'items': {'type': 'string'},
|
||||
},
|
||||
'all_sign_to_complete': {
|
||||
'type': 'string',
|
||||
'description': 'Every recipient has to sign',
|
||||
'enum': ['true', 'false'],
|
||||
|
||||
'default': 'false',
|
||||
},
|
||||
'user_sign_first': {
|
||||
'type': 'string',
|
||||
'description': 'the author must sign first',
|
||||
'enum': ['true', 'false'],
|
||||
'default': 'false',
|
||||
},
|
||||
'pending': {
|
||||
'type': 'string',
|
||||
'description': 'Pending',
|
||||
'enum': ['true', 'false'],
|
||||
'default': 'true',
|
||||
},
|
||||
'force_all_sign': {
|
||||
'type': 'string',
|
||||
'description': 'Force signing on every document',
|
||||
'enum': ['true', 'false'],
|
||||
'default': 'false',
|
||||
},
|
||||
'comment': {'type': 'string', 'description': 'Comment'},
|
||||
'sign_type': {
|
||||
'type': 'string',
|
||||
'description': 'Signature type',
|
||||
'enum': ['visa', 'pdfImageStamp', 'certSign', 'nexuSign'],
|
||||
'default': 'pdfImageStamp',
|
||||
},
|
||||
'create_by_eppn': {'type': 'string', 'description': 'EPPN of the sign request owner'},
|
||||
'title': {'type': 'string', 'description': 'Title'},
|
||||
'target_url': {
|
||||
'type': 'string',
|
||||
'description': 'End location',
|
||||
},
|
||||
}
|
||||
),
|
||||
}
|
||||
pmarillonnet
commented
Typo ici (s/WTIH/WITH), à reporter dans le test aussi. Typo ici (s/WTIH/WITH), à reporter dans le test aussi.
ecazenave
commented
Corrigé. Corrigé.
|
||||
|
||||
|
||||
SIGN_REQUEST_WITH_WORKFLOW_SCHEMA = {
|
||||
'$schema': 'http://json-schema.org/draft-04/schema#',
|
||||
'title': '',
|
||||
'description': '',
|
||||
'type': 'object',
|
||||
'required': ['file', 'eppn', 'workflow_id'],
|
||||
'unflatten': True,
|
||||
'properties': collections.OrderedDict(
|
||||
{
|
||||
'file': {
|
||||
'type': 'object',
|
||||
'description': 'File object',
|
||||
'required': ['filename', 'content_type', 'content'],
|
||||
'properties': {
|
||||
'filename': {
|
||||
'type': 'string',
|
||||
},
|
||||
'content_type': {
|
||||
'type': 'string',
|
||||
'description': 'MIME content-type',
|
||||
},
|
||||
'content': {
|
||||
'type': 'string',
|
||||
'description': 'Content, base64 encoded',
|
||||
},
|
||||
},
|
||||
},
|
||||
'recipients_emails': {
|
||||
'type': 'array',
|
||||
'description': 'Recipients emails at each step',
|
||||
'items': {'type': 'string'},
|
||||
},
|
||||
'target_emails': {
|
||||
'type': 'array',
|
||||
'description': 'Target emails',
|
||||
'items': {'type': 'string'},
|
||||
},
|
||||
'all_sign_to_completes': {
|
||||
'type': 'array',
|
||||
'description': 'Steps numbers were every recipient has to sign',
|
||||
'items': {'type': 'string'},
|
||||
},
|
||||
'eppn': {'type': 'string', 'description': 'EPPN of the sign request owner'},
|
||||
'workflow_id': {'type': 'string', 'description': 'Identifier of the workflow'},
|
||||
'title': {'type': 'string', 'description': 'Title'},
|
||||
'target_urls': {
|
||||
'type': 'array',
|
||||
'description': 'End locations',
|
||||
'items': {'type': 'string'},
|
||||
},
|
||||
'signrequest_params_jsonstring': {
|
||||
'type': 'string',
|
||||
'description': 'Signature parameters',
|
||||
},
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def clean_list(some_list):
|
||||
return [elem for elem in some_list if elem]
|
||||
|
||||
|
||||
def to_bool(some_str):
|
||||
return some_str == 'true'
|
||||
|
||||
|
||||
class EsupSignature(BaseResource, HTTPResource):
|
||||
base_url = models.URLField(_('API URL'))
|
||||
|
||||
|
@ -77,12 +184,12 @@ class EsupSignature(BaseResource, HTTPResource):
|
|||
class Meta:
|
||||
verbose_name = _('Esup Signature')
|
||||
|
||||
def _call(self, path, method='get', data=None, files=None, expect_json=True):
|
||||
def _call(self, path, method='get', params=None, files=None, expect_json=True):
|
||||
url = urllib.parse.urljoin(self.base_url, path)
|
||||
kwargs = {}
|
||||
|
||||
if method == 'post':
|
||||
kwargs['data'] = data
|
||||
kwargs['params'] = params
|
||||
kwargs['files'] = files
|
||||
|
||||
try:
|
||||
|
@ -125,8 +232,18 @@ class EsupSignature(BaseResource, HTTPResource):
|
|||
'recipients_emails/0': 'xx@foo.com',
|
||||
'recipients_emails/1': 'yy@foo.com',
|
||||
'recipients_emails/2': 'zz@foo.com',
|
||||
'eppn': 'aa@foo.com',
|
||||
'recipients_cc_emails/0': 'xx@foo.com',
|
||||
'recipients_cc_emails/1': 'yy@foo.com',
|
||||
'recipients_cc_emails/2': 'zz@foo.com',
|
||||
'all_sign_to_complete': 'true',
|
||||
'user_sign_first': 'false',
|
||||
'pending': 'true',
|
||||
'force_all_sign': 'false',
|
||||
'comment': 'a comment',
|
||||
'sign_type': 'pdfImageStamp',
|
||||
'create_by_eppn': 'aa@foo.com',
|
||||
'title': 'a title',
|
||||
'target_url': 'smb://foo.bar/location-1/',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -143,16 +260,92 @@ class EsupSignature(BaseResource, HTTPResource):
|
|||
)
|
||||
}
|
||||
|
||||
recipients_emails = [email for email in post_data['recipients_emails'] if email]
|
||||
data = {
|
||||
'signType': 'pdfImageStamp',
|
||||
'recipientsEmails': recipients_emails,
|
||||
'eppn': post_data['eppn'],
|
||||
params = {
|
||||
'recipientsEmails': clean_list(post_data['recipients_emails']),
|
||||
'recipientsCCEmails': clean_list(post_data.get('recipients_cc_emails', [])),
|
||||
'comment': post_data.get('comment', ''),
|
||||
'signType': post_data.get('sign_type', 'pdfImageStamp'),
|
||||
'createByEppn': post_data['create_by_eppn'],
|
||||
'title': post_data.get('title', ''),
|
||||
'pending': True,
|
||||
'targetUrl': post_data.get('target_url', ''),
|
||||
}
|
||||
|
||||
return {'data': self._call('ws/signrequests/new', method='post', data=data, files=files)}
|
||||
bool_params = {
|
||||
pmarillonnet
commented
Du détail, mais pas compris pourquoi la valeur par défaut est en dur ici et ne reprend pas celle définie dans le schéma. Du détail, mais pas compris pourquoi la valeur par défaut est en dur ici et ne reprend pas celle définie dans le schéma.
ecazenave
commented
Dans la norme JSON schema : "The default keyword specifies a default value. This value is not used to fill in missing values during the validation process." Charge donc à l'application de "fournir" les valeurs par défaut. Dans la norme JSON schema : "The default keyword specifies a default value. This value is not used to fill in missing values during the validation process."
Charge donc à l'application de "fournir" les valeurs par défaut.
pmarillonnet
commented
Cool, j’ignorais ça, merci pour la clarification. Cool, j’ignorais ça, merci pour la clarification.
|
||||
'all_sign_to_complete': ('allSignToComplete', False),
|
||||
'user_sign_first': ('userSignFirst', False),
|
||||
'pending': ('pending', True),
|
||||
'force_all_sign': ('forceAllSign', False),
|
||||
}
|
||||
for key, value in bool_params.items():
|
||||
ext_param, default = value
|
||||
params[ext_param] = default
|
||||
if key in post_data:
|
||||
params[ext_param] = to_bool(post_data[key])
|
||||
|
||||
return {'data': self._call('ws/signrequests/new', method='post', params=params, files=files)}
|
||||
|
||||
@endpoint(
|
||||
name='new-with-workflow',
|
||||
description=_('Create a sign request'),
|
||||
perm='can_access',
|
||||
post={
|
||||
'request_body': {
|
||||
'schema': {
|
||||
'application/json': SIGN_REQUEST_WITH_WORKFLOW_SCHEMA,
|
||||
}
|
||||
},
|
||||
'input_example': {
|
||||
'file': {
|
||||
'filename': 'example-1.pdf',
|
||||
'content_type': 'application/pdf',
|
||||
'content': 'JVBERi0xL...(base64 PDF)...',
|
||||
},
|
||||
'workflow_id': '99',
|
||||
'eppn': 'aa@foo.com',
|
||||
'title': 'a title',
|
||||
'recipients_emails/0': '0*xx@foo.com',
|
||||
'recipients_emails/1': '0*yy@foo.com',
|
||||
'recipients_emails/2': '1*zz@foo.com',
|
||||
'all_sign_to_completes/0': '12',
|
||||
'all_sign_to_completes/1': '13',
|
||||
'target_emails/0': 'xx@foo.com',
|
||||
'target_emails/1': 'yy@foo.com',
|
||||
'target_emails/2': 'zz@foo.com',
|
||||
'signrequest_params_jsonstring': 'List [ OrderedMap { "xPos": 100, "yPos": 100, "signPageNumber": 1 }, '
|
||||
'OrderedMap { "xPos": 200, "yPos": 200, "signPageNumber": 1 } ]',
|
||||
'target_urls/0': 'smb://foo.bar/location-1/',
|
||||
'target_urls/1': 'smb://foo.bar/location-2/',
|
||||
},
|
||||
},
|
||||
)
|
||||
def new_with_workflow(self, request, post_data):
|
||||
try:
|
||||
file_bytes = io.BytesIO(base64.b64decode(post_data['file']['content']))
|
||||
except (TypeError, binascii.Error):
|
||||
raise APIError("Can't decode file")
|
||||
files = {
|
||||
'multipartFiles': (
|
||||
post_data['file']['filename'],
|
||||
file_bytes,
|
||||
post_data['file']['content_type'],
|
||||
)
|
||||
}
|
||||
|
||||
params = {
|
||||
'createByEppn': post_data['eppn'],
|
||||
'title': post_data.get('title', ''),
|
||||
'recipientsEmails': clean_list(post_data.get('recipients_emails', [])),
|
||||
'allSignToCompletes': clean_list(post_data.get('all_sign_to_completes', [])),
|
||||
'targetEmails': clean_list(post_data.get('target_emails', [])),
|
||||
'signRequestParamsJsonString': post_data.get('signrequest_params_jsonstring', ''),
|
||||
'targetUrls': clean_list(post_data.get('target_urls', [])),
|
||||
}
|
||||
|
||||
return {
|
||||
'data': self._call(
|
||||
'/ws/workflows/%s/new' % post_data['workflow_id'], method='post', params=params, files=files
|
||||
)
|
||||
}
|
||||
|
||||
@endpoint(
|
||||
methods=['get'],
|
||||
|
|
|
@ -31,11 +31,26 @@ def test_new(app, connector):
|
|||
},
|
||||
'recipients_emails/0': 'foo@invalid',
|
||||
'recipients_emails/1': 'bar@invalid',
|
||||
'eppn': 'baz@invalid',
|
||||
'create_by_eppn': 'baz@invalid',
|
||||
'title': 'a title',
|
||||
}
|
||||
with responses.RequestsMock() as rsps:
|
||||
rsps.post('https://esup-signature.invalid/ws/signrequests/new', status=200, json=9)
|
||||
query_params = {
|
||||
'recipientsEmails': ['foo@invalid', 'bar@invalid'],
|
||||
'createByEppn': 'baz@invalid',
|
||||
'title': 'a title',
|
||||
'signType': 'pdfImageStamp',
|
||||
'pending': True,
|
||||
'allSignToComplete': False,
|
||||
'userSignFirst': False,
|
||||
'forceAllSign': False,
|
||||
}
|
||||
rsps.post(
|
||||
'https://esup-signature.invalid/ws/signrequests/new',
|
||||
match=[responses.matchers.query_param_matcher(query_params)],
|
||||
status=200,
|
||||
json=9,
|
||||
)
|
||||
resp = app.post_json('/esup-signature/esup-signature/new', params=params)
|
||||
assert len(rsps.calls) == 1
|
||||
assert rsps.calls[0].request.headers['Content-Type'].startswith('multipart/form-data')
|
||||
|
@ -44,6 +59,54 @@ def test_new(app, connector):
|
|||
assert json_resp['data'] == 9
|
||||
|
||||
|
||||
def test_new_with_workflow(app, connector):
|
||||
params = {
|
||||
'file': {
|
||||
'filename': 'bla',
|
||||
'content': base64.b64encode(b'who what').decode(),
|
||||
'content_type': 'text/plain',
|
||||
},
|
||||
'workflow_id': '99',
|
||||
'eppn': 'aa@foo.com',
|
||||
'title': 'a title',
|
||||
'recipients_emails/0': '0*xx@foo.com',
|
||||
'recipients_emails/1': '0*yy@foo.com',
|
||||
'recipients_emails/2': '1*zz@foo.com',
|
||||
'all_sign_to_completes/0': '12',
|
||||
'all_sign_to_completes/1': '13',
|
||||
'target_emails/0': 'xx@foo.com',
|
||||
'target_emails/1': 'yy@foo.com',
|
||||
'target_emails/2': 'zz@foo.com',
|
||||
'signrequest_params_jsonstring': 'List [ OrderedMap { "xPos": 100, "yPos": 100, "signPageNumber": 1 }, '
|
||||
'OrderedMap { "xPos": 200, "yPos": 200, "signPageNumber": 1 } ]',
|
||||
'target_urls/0': 'smb://foo.bar/location-1/',
|
||||
'target_urls/1': 'smb://foo.bar/location-2/',
|
||||
}
|
||||
with responses.RequestsMock() as rsps:
|
||||
query_params = {
|
||||
'createByEppn': 'aa@foo.com',
|
||||
'title': 'a title',
|
||||
'recipientsEmails': ['0*xx@foo.com', '0*yy@foo.com', '1*zz@foo.com'],
|
||||
'allSignToCompletes': ['12', '13'],
|
||||
'targetEmails': ['xx@foo.com', 'yy@foo.com', 'zz@foo.com'],
|
||||
'signRequestParamsJsonString': 'List [ OrderedMap { "xPos": 100, "yPos": 100, "signPageNumber": 1 }, '
|
||||
'OrderedMap { "xPos": 200, "yPos": 200, "signPageNumber": 1 } ]',
|
||||
'targetUrls': ['smb://foo.bar/location-1/', 'smb://foo.bar/location-2/'],
|
||||
}
|
||||
rsps.post(
|
||||
'https://esup-signature.invalid/ws/workflows/99/new',
|
||||
match=[responses.matchers.query_param_matcher(query_params)],
|
||||
status=200,
|
||||
json=9,
|
||||
)
|
||||
resp = app.post_json('/esup-signature/esup-signature/new-with-workflow', params=params)
|
||||
assert len(rsps.calls) == 1
|
||||
assert rsps.calls[0].request.headers['Content-Type'].startswith('multipart/form-data')
|
||||
json_resp = resp.json
|
||||
assert json_resp['err'] == 0
|
||||
assert json_resp['data'] == 9
|
||||
|
||||
|
||||
def test_status(app, connector):
|
||||
with responses.RequestsMock() as rsps:
|
||||
rsps.get('https://esup-signature.invalid/ws/signrequests/1', status=200, json={'status': 'completed'})
|
||||
|
|
Loading…
Reference in New Issue
Est-ce que pour plus de clarté on n’a pas intérêt à décider d’une valeur par défaut pour tous ces paramètres booléens nouvellement ajoutés au schéma ?
Fait.