Files
enterprise-addons/sign/tests/test_sign_request.py
T

706 lines
45 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from dateutil.relativedelta import relativedelta
from odoo import Command, fields
from odoo.exceptions import UserError, ValidationError
from odoo.tests import Form, users
from odoo.tools import formataddr
from odoo.addons.mail.tests.common import MockEmail
from .sign_request_common import SignRequestCommon, freeze_time
from unittest.mock import patch, PropertyMock, Mock
from datetime import timedelta
class TestSignRequest(SignRequestCommon, MockEmail):
def test_sign_request_create(self):
sign_request_no_item = self.create_sign_request_no_item(signer=self.partner_1, cc_partners=self.partner_4)
sign_request_3_roles = self.create_sign_request_3_roles(signer_1=self.partner_1, signer_2=self.partner_2, signer_3=self.partner_3, cc_partners=self.partner_4)
for sign_request in [sign_request_no_item, sign_request_3_roles]:
self.assertTrue(sign_request.exists(), 'A sign request with no sign item should be created')
self.assertEqual(sign_request.state, 'sent', 'The default state for a new created sign request should be "sent"')
self.assertTrue(all(sign_request.request_item_ids.mapped('is_mail_sent')), 'The mail should be sent for the new created sign request by default')
self.assertEqual(sign_request.with_context(active_test=False).cc_partner_ids, self.partner_4, 'The cc_partners should be the specified one and the creator unless the creator is inactive')
self.assertEqual(len(sign_request.sign_log_ids.filtered(lambda log: log.action == 'create')), 1, 'A log with action="create" should be created')
for sign_request_item in sign_request:
self.assertEqual(sign_request_item.state, 'sent', 'The default state for a new created sign request item should be "sent"')
self.assertEqual(len(sign_request_no_item.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_1.id)), 1, 'An activity should be scheduled for signers with Sign Access')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_1.id)), 1, 'An activity should be scheduled for signers with Sign Access')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_2.id)), 1, 'An activity should be scheduled for signers with Sign Access')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_5.id)), 0, 'An activity should not be scheduled for signers without Sign Access')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_4.id)), 0, 'An activity should not be scheduled for CC partners')
SignRequest = self.env['sign.request']
with self.assertRaises(ValidationError, msg='A sign request with no sign item needs a signer'):
SignRequest.create({
'template_id': self.template_no_item.id,
'reference': self.template_no_item.display_name,
})
with self.assertRaises(ValidationError, msg='A sign request with no sign item can only have the default role'):
SignRequest.create({
'template_id': self.template_no_item.id,
'request_item_ids': [Command.create({
'partner_id': self.partner_1.id,
'role_id': self.role_signer_3.id,
})],
'reference': self.template_no_item.display_name,
})
with self.assertRaises(ValidationError, msg='Three roles need three singers'):
SignRequest.create({
'template_id': self.template_3_roles.id,
'request_item_ids': [Command.create({
'partner_id': self.partner_1.id,
'role_id': self.role_signer_1.id,
}), Command.create({
'partner_id': self.partner_2.id,
'role_id': self.role_signer_2.id,
})],
'reference': self.template_3_roles.display_name,
})
with self.assertRaises(ValidationError, msg='A role cannot be shared with two signers'):
SignRequest.create({
'template_id': self.template_3_roles.id,
'request_item_ids': [Command.create({
'partner_id': self.partner_1.id,
'role_id': self.role_signer_1.id,
}), Command.create({
'partner_id': self.partner_2.id,
'role_id': self.role_signer_2.id,
}), Command.create({
'partner_id': self.partner_3.id,
'role_id': self.role_signer_3.id,
}), Command.create({
'partner_id': self.partner_4.id,
'role_id': self.role_signer_3.id,
})],
'reference': self.template_3_roles.display_name,
})
def test_sign_request_no_item_create_sign_cancel_copy(self):
# create
sign_request_no_item = self.create_sign_request_no_item(signer=self.partner_1, cc_partners=self.partner_4)
sign_request_item = sign_request_no_item.request_item_ids[0]
# sign
with self.assertRaises(UserError, msg='A sign.request.item can only sign its sign.items'):
sign_request_item.sign(self.signer_1_sign_values)
sign_request_item.sign(self.signature_fake)
self.assertEqual(sign_request_item.state, 'completed', 'The sign.request.item should be completed')
self.assertEqual(sign_request_no_item.state, 'signed', 'The sign request should be signed')
self.assertEqual(len(sign_request_no_item.completed_document_attachment_ids), 2, 'The completed document and the certificate should be created')
self.assertEqual(len(sign_request_no_item.sign_log_ids.filtered(
lambda log: log.action == 'sign' and log.sign_request_item_id == sign_request_item)),
1, 'A log with action="sign" should be created')
self.assertEqual(len(sign_request_no_item.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_1.id)), 0, 'The activity should be removed after signing')
with self.assertRaises(UserError, msg='A document cannot be signed twice'):
sign_request_item.sign(self.signature_fake)
# unlink
with self.assertRaises(UserError, msg='A signed sign request cannot be unlinked'):
sign_request_no_item.unlink()
# cancel
sign_request_item_token = sign_request_item.access_token
sign_request_no_item_token = sign_request_no_item.access_token
sign_request_no_item.cancel()
self.assertEqual(sign_request_item.state, 'completed', 'The sign.request.item should be completed')
# copy
new_sign_request_no_item = sign_request_no_item.copy()
self.assertTrue(new_sign_request_no_item.exists(), 'A sign request with no sign item should be created')
self.assertEqual(new_sign_request_no_item.state, 'sent', 'The default state for a new created sign request should be "sent"')
self.assertTrue(all(new_sign_request_no_item.request_item_ids.mapped('is_mail_sent')), 'The mail should be sent for the new created sign request by default')
self.assertEqual(new_sign_request_no_item.with_context(active_test=False).cc_partner_ids, self.partner_4, 'The cc_partners should be the specified one and the creator unless he is inactive')
self.assertEqual(len(new_sign_request_no_item.sign_log_ids.filtered(lambda log: log.action == 'create')), 1, 'A log with action="create" should be created')
for sign_request_item in new_sign_request_no_item:
self.assertEqual(sign_request_item.state, 'sent', 'The default state for a new created sign request item should be "sent"')
self.assertNotEqual(new_sign_request_no_item.access_token, sign_request_no_item.access_token, 'The access_token should be changed')
self.assertNotEqual(new_sign_request_no_item.request_item_ids[0].access_token, sign_request_no_item.request_item_ids[0].access_token, 'The access_token should be changed')
def test_sign_request_3_roles_create_sign_cancel(self):
# create
sign_request_3_roles = self.create_sign_request_3_roles(signer_1=self.partner_1, signer_2=self.partner_2, signer_3=self.partner_3, cc_partners=self.partner_4)
role2sign_request_item = dict([(sign_request_item.role_id, sign_request_item) for sign_request_item in sign_request_3_roles.request_item_ids])
sign_request_item_signer_1 = role2sign_request_item[self.role_signer_1]
sign_request_item_signer_2 = role2sign_request_item[self.role_signer_2]
sign_request_item_signer_3 = role2sign_request_item[self.role_signer_3]
# sign
with self.assertRaises(UserError, msg='A sign.request.item can only sign its sign.items'):
sign_request_item_signer_2.sign(self.signer_1_sign_values)
sign_request_item_signer_1.sign(self.signer_1_sign_values)
self.assertEqual(sign_request_item_signer_1.state, 'completed', 'The sign.request.item should be completed')
self.assertEqual(sign_request_item_signer_2.state, 'sent', 'The sign.request.item should be sent')
self.assertEqual(sign_request_item_signer_3.state, 'sent', 'The sign.request.item should be sent')
self.assertEqual(sign_request_3_roles.state, 'sent', 'The sign request should be signed')
self.assertEqual(len(sign_request_3_roles.sign_log_ids.filtered(
lambda log: log.action == 'sign' and log.sign_request_item_id == sign_request_item_signer_1)),
1, 'A log with action="sign" should be created')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_1.id)), 0, 'The activity should be removed after signing')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_2.id)), 1, 'The activity should not be removed for unsigned signer')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_3.id)), 1, 'The activity should not be removed for unsigned signer')
with self.assertRaises(UserError, msg='A document cannot be signed twice'):
sign_request_item_signer_1.sign(self.signer_1_sign_values)
# cancel
sign_request_item_signer_1_token = sign_request_item_signer_1.access_token
sign_request_item_signer_2_token = sign_request_item_signer_2.access_token
sign_request_item_signer_3_token = sign_request_item_signer_3.access_token
sign_request_3_roles_token = sign_request_3_roles.access_token
sign_request_3_roles.cancel()
self.assertEqual(sign_request_item_signer_1.state, 'completed', 'The sign.request.item should be completed')
self.assertEqual(sign_request_item_signer_2.state, 'canceled', 'The sign.request.item should be canceled')
self.assertEqual(sign_request_item_signer_3.state, 'canceled', 'The sign.request.item should be canceled')
self.assertEqual(sign_request_3_roles.state, 'canceled', 'The sign request should be canceled')
self.assertNotEqual(sign_request_item_signer_1.access_token, sign_request_item_signer_1_token, 'The access token should be changed')
self.assertNotEqual(sign_request_item_signer_2.access_token, sign_request_item_signer_2_token, 'The access token should be changed')
self.assertNotEqual(sign_request_item_signer_3.access_token, sign_request_item_signer_3_token, 'The access token should be changed')
self.assertNotEqual(sign_request_3_roles.access_token, sign_request_3_roles_token, 'The access token should be changed')
self.assertEqual(len(sign_request_3_roles.sign_log_ids.filtered(lambda log: log.action == 'cancel')), 1, 'A log with action="cancel" should be created')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_2.id)), 0, 'The activity should be removed after cancellation')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_3.id)), 0, 'The activity should be removed after cancellation')
def test_sign_request_3_roles_create_sign_refuse_cancel(self):
# create
sign_request_3_roles = self.create_sign_request_3_roles(signer_1=self.partner_1, signer_2=self.partner_2, signer_3=self.partner_3, cc_partners=self.partner_4)
role2sign_request_item = dict([(sign_request_item.role_id, sign_request_item) for sign_request_item in sign_request_3_roles.request_item_ids])
sign_request_item_signer_1 = role2sign_request_item[self.role_signer_1]
sign_request_item_signer_2 = role2sign_request_item[self.role_signer_2]
sign_request_item_signer_3 = role2sign_request_item[self.role_signer_3]
# sign (test has been done in test_sign_request_3_roles_create_sign_cancel)
sign_request_item_signer_1.sign(self.signer_1_sign_values)
# refuse
with self.assertRaises(UserError, msg='A signed sign.request.item cannot be refused'):
sign_request_item_signer_1._refuse(request_state='sent', refusal_reason="bad document")
sign_request_item_signer_1_token = sign_request_item_signer_1.access_token
sign_request_item_signer_2_token = sign_request_item_signer_2.access_token
sign_request_item_signer_3_token = sign_request_item_signer_3.access_token
sign_request_3_roles_token = sign_request_3_roles.access_token
sign_request_item_signer_2._refuse(request_state='sent', refusal_reason='bad document')
self.assertEqual(sign_request_item_signer_1.state, 'completed', 'The sign.request.item should be completed')
self.assertEqual(sign_request_item_signer_2.state, 'canceled', 'The sign.request.item should be completed')
self.assertEqual(sign_request_item_signer_3.state, 'canceled', 'The sign.request.item should be canceled')
self.assertEqual(sign_request_3_roles.state, 'canceled', 'The sign request should be canceled')
self.assertNotEqual(sign_request_item_signer_1.access_token, sign_request_item_signer_1_token, 'The access token should be changed')
self.assertNotEqual(sign_request_item_signer_2.access_token, sign_request_item_signer_2_token, 'The access token should be changed')
self.assertNotEqual(sign_request_item_signer_3.access_token, sign_request_item_signer_3_token, 'The access token should be changed')
self.assertNotEqual(sign_request_3_roles.access_token, sign_request_3_roles_token, 'The access token should be changed')
self.assertEqual(len(sign_request_3_roles.sign_log_ids.filtered(
lambda log: log.action == 'refuse' and log.sign_request_item_id == sign_request_item_signer_2)),
1, 'A log with action="refuse" should be created')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_2.id)), 0, 'The activity should be removed for refused signer')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_3.id)), 0, 'The activity should be removed for remaining signers')
with self.assertRaises(UserError, msg='A canceled sign.request.item cannot be signed'):
sign_request_item_signer_3.sign(self.signer_3_sign_values)
# cancel
sign_request_3_roles.cancel()
self.assertEqual(sign_request_item_signer_1.state, 'completed', 'The sign.request.item should be completed')
self.assertEqual(sign_request_item_signer_2.state, 'canceled', 'The sign.request.item should be completed')
self.assertEqual(sign_request_item_signer_3.state, 'canceled', 'The sign.request.item should be canceled')
self.assertEqual(sign_request_3_roles.state, 'canceled', 'The sign request should be canceled')
self.assertNotEqual(sign_request_item_signer_1.access_token, sign_request_item_signer_1_token, 'The access token should be changed')
self.assertNotEqual(sign_request_item_signer_2.access_token, sign_request_item_signer_2_token, 'The access token should be changed')
self.assertNotEqual(sign_request_item_signer_3.access_token, sign_request_item_signer_3_token, 'The access token should be changed')
self.assertNotEqual(sign_request_3_roles.access_token, sign_request_3_roles_token, 'The access token should be changed')
# now the cancel method is also called from refuse method so the log count is become 2
self.assertEqual(len(sign_request_3_roles.sign_log_ids.filtered(lambda log: log.action == 'cancel')), 2, 'A log with action="cancel" should be created')
def test_sign_request_refuse_shared(self):
""" Ensure that shared sign requests can be refused by public users. """
# Get the shared request from a template with one role.
wizard_id = self.template_1_role.open_shared_sign_request()['res_id']
wizard = self.env['sign.request.share'].browse(wizard_id)
shared_request = wizard.sign_request_id
sign_request_item = shared_request.request_item_ids[0]
with self.assertRaises(UserError):
# Ensure an user error is raised by not specifying the refusal email.
sign_request_item.with_user(self.public_user).sudo()._refuse(
request_state="shared",
refusal_reason="Reason",
refusal_name="Marc"
)
sign_request_item.with_user(self.public_user).sudo()._refuse(
request_state="shared",
refusal_reason="Reason",
refusal_name="Marc",
refusal_email="demo@odoo.com"
)
self.assertEqual(shared_request.state, "shared", "Previous request must remain shared.")
refused_public_user = self.env['res.partner'].search([('email', '=', 'demo@odoo.com')])
self.assertTrue(refused_public_user, "Ensure that the partner from the public user was created.")
refused_request_item = self.env['sign.request.item'].search([('partner_id', '=', refused_public_user.id)])
self.assertEqual(len(refused_request_item), 1, "Ensure that the refused request item was created.")
refused_request = self.env['sign.request'].search([
('state', '=', 'canceled'),
('id', '>', shared_request.id)
])
self.assertEqual(len(refused_request), 1, "Ensure that the refused request was created.")
def test_sign_request_item_auto_resend(self):
# create
sign_request = self.create_sign_request_no_item(signer=self.partner_1, cc_partners=self.partner_4)
request_item_ids = sign_request.request_item_ids
request_item = request_item_ids[0]
token_a = request_item.access_token
self.assertEqual(request_item.signer_email, "laurie.poiret.a@example.com", 'email address should be laurie.poiret.a@example.com')
self.assertEqual(request_item.is_mail_sent, True, 'email should be sent')
# resend the document
request_item.send_signature_accesses()
self.assertEqual(request_item.access_token, token_a, "sign request item's access token should not be changed")
# change the email address of the signer (laurie.poiret.b)
with self.assertRaises(ValidationError, msg='All signers must have valid email addresses'):
self.partner_1.write({'email': 'laurie.poiret.b'})
# change the email address to upper case (LAURIE.POIRET.A@example.com)
self.partner_1.write({'email': 'LAURIE.POIRET.A@example.com'})
self.assertEqual(request_item.signer_email, "laurie.poiret.a@example.com", 'email address should not be changed as is the same email')
self.assertFalse(
sign_request.sign_log_ids.filtered(lambda log: log.action == 'update_mail' and log.sign_request_item_id == request_item),
'No log with action="update_mail" should be created after changing the email to upper case'
)
# change the email address of the signer (laurie.poiret.b@example.com)
self.partner_1.write({'email': 'laurie.poiret.b@example.com'})
token_b = request_item.access_token
self.assertEqual(request_item.signer_email, "laurie.poiret.b@example.com", 'email address should be laurie.poiret.b@example.com')
self.assertNotEqual(token_b, token_a, "sign request item's access token should be changed")
self.assertEqual(len(sign_request.sign_log_ids.filtered(
lambda log: log.action == 'update_mail' and log.sign_request_item_id == request_item)),
1, 'A log with action="update_mail" should be created')
self.assertEqual(len(sign_request.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_1.id)), 1, 'The number of activities should still be 1')
# sign the document
request_item.sign(self.signature_fake)
self.assertEqual(request_item.signer_email, "laurie.poiret.b@example.com", 'email address should be laurie.poiret.b@example.com')
# change the email address of the signer (laurie.poiret.c@example.com)
self.partner_1.write({'email': 'laurie.poiret.c@example.com'})
token_c = request_item.access_token
self.assertEqual(request_item.signer_email, "laurie.poiret.b@example.com", 'email address should be laurie.poiret.b@example.com')
self.assertEqual(token_c, token_b, "sign request item's access token should be not changed after the document is signed by the signer")
self.assertEqual(len(sign_request.sign_log_ids.filtered(
lambda log: log.action == 'update_mail' and log.sign_request_item_id == request_item)),
1, 'No new log with action="update_mail" should be created')
def test_sign_request_item_reassign_sign_reassign_refuse_reassign(self):
# create
sign_request_3_roles = self.create_sign_request_3_roles(signer_1=self.partner_1, signer_2=self.partner_2,
signer_3=self.partner_3, cc_partners=self.partner_4)
role2sign_request_item = dict([(sign_request_item.role_id, sign_request_item) for sign_request_item in sign_request_3_roles.request_item_ids])
sign_request_item_signer_1 = role2sign_request_item[self.role_signer_1]
sign_request_item_signer_2 = role2sign_request_item[self.role_signer_2]
sign_request_item_signer_3 = role2sign_request_item[self.role_signer_3]
# reassign
self.assertEqual(sign_request_item_signer_1.signer_email, "laurie.poiret.a@example.com", 'email address should be laurie.poiret.a@example.com')
self.assertEqual(sign_request_item_signer_1.is_mail_sent, True, 'email should be sent')
token_signer_1 = sign_request_item_signer_1.access_token
with self.assertRaises(UserError, msg='Reassigning a role without change_authorized is not allowed'):
sign_request_item_signer_1.write({'partner_id': self.partner_5.id})
sign_request_item_signer_1.role_id.change_authorized = True
with self.assertRaises(UserError, msg='Reassigning the partner_id to False is not allowed'):
sign_request_item_signer_1.write({'partner_id': False})
logs_num = len(sign_request_3_roles.sign_log_ids)
sign_request_item_signer_1.write({'partner_id': self.partner_5.id})
self.assertEqual(sign_request_item_signer_1.signer_email, "char.aznable.a@example.com", 'email address should be char.aznable.a@example.com')
self.assertNotEqual(sign_request_item_signer_1.access_token, token_signer_1, "sign request item's access token should be changed")
self.assertEqual(sign_request_item_signer_1.is_mail_sent, False, 'email should not be sent')
self.assertEqual(len(sign_request_3_roles.sign_log_ids), logs_num, 'No new log should be created')
self.assertEqual(sign_request_3_roles.with_context(active_test=False).cc_partner_ids, self.partner_4 + self.partner_1, 'If a signer is reassigned and no longer be a signer, he should be a contact in copy')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_1.id)), 0, 'The activity for the old signer should be removed')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_5.id)), 0, 'No activity should be created for user without permission to access Sign')
# sign
sign_request_item_signer_1.sign(self.signer_1_sign_values)
# reassign
token_signer_2 = sign_request_item_signer_1.access_token
with self.assertRaises(UserError, msg='A signed sign request item cannot be reassigned'):
sign_request_item_signer_1.write({'partner_id': self.partner_1.id})
sign_request_item_signer_2.role_id.change_authorized = True
logs_num = len(sign_request_3_roles.sign_log_ids)
sign_request_item_signer_2.write({'partner_id': self.partner_1.id})
self.assertEqual(sign_request_item_signer_2.signer_email, "laurie.poiret.a@example.com", 'email address should be laurie.poiret.a@example.com')
self.assertNotEqual(sign_request_item_signer_2.access_token, token_signer_2, "sign request item's access token should be changed")
self.assertEqual(sign_request_item_signer_2.is_mail_sent, False, 'email should not be sent')
self.assertEqual(len(sign_request_3_roles.sign_log_ids), logs_num, 'No new log should be created')
self.assertEqual(sign_request_3_roles.with_context(active_test=False).cc_partner_ids, self.partner_4 + self.partner_2, 'If a signer is reassigned and no longer be a signer, he should be a contact in copy')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_2.id)), 0, 'The activity for the old signer should be removed')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_1.id)), 1, 'An activity for the new signer should be created')
# refuse
sign_request_item_signer_2._refuse(request_state='sent', refusal_reason='bad request')
# reassign
with self.assertRaises(UserError, msg='A refused sign request item cannot be reassigned'):
sign_request_item_signer_2.write({'partner_id': self.partner_2.id})
with self.assertRaises(UserError, msg='A canceled sign request item cannot be reassigned'):
sign_request_item_signer_3.write({'partner_id': self.partner_2.id})
def test_sign_request_mail_sent_order(self):
sign_request_3_roles = self.env['sign.request'].create({
'template_id': self.template_3_roles.id,
'reference': self.template_3_roles.display_name,
'request_item_ids': [Command.create({
'partner_id': self.partner_1.id,
'role_id': self.role_signer_1.id,
'mail_sent_order': 1,
}), Command.create({
'partner_id': self.partner_2.id,
'role_id': self.role_signer_2.id,
'mail_sent_order': 2,
}), Command.create({
'partner_id': self.partner_3.id,
'role_id': self.role_signer_3.id,
'mail_sent_order': 2,
})],
})
self.partner_4.user_ids.notification_type = 'inbox'
sign_request_3_roles.message_subscribe(partner_ids=[self.partner_4.id])
role2sign_request_item = dict([(sign_request_item.role_id, sign_request_item) for sign_request_item in sign_request_3_roles.request_item_ids])
sign_request_item_signer_1 = role2sign_request_item[self.role_signer_1]
sign_request_item_signer_2 = role2sign_request_item[self.role_signer_2]
sign_request_item_signer_3 = role2sign_request_item[self.role_signer_3]
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_1.id)), 1, 'An activity should be scheduled for the first signer')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_2.id)), 0, 'No activity should be scheduled for the second signer')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_3.id)), 0, 'No activity should be scheduled for the third signer')
self.assertTrue(sign_request_item_signer_1.is_mail_sent, 'An email should be sent for the first signer')
self.assertFalse(sign_request_item_signer_2.is_mail_sent, 'No email should be sent for the second signer')
self.assertFalse(sign_request_item_signer_3.is_mail_sent, 'No email should be sent for the third signer')
# sign
sign_request_item_signer_1.sign(self.signer_1_sign_values)
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_2.id)), 1, 'An activity should be scheduled for the second signer')
self.assertEqual(len(sign_request_3_roles.activity_search(['sign.mail_activity_data_signature_request'], user_id=self.user_3.id)), 1, 'An activity should be scheduled for the third signer')
self.assertTrue(sign_request_item_signer_2.is_mail_sent, 'An email should be sent for the second signer')
self.assertTrue(sign_request_item_signer_3.is_mail_sent, 'An email should be sent for the third signer')
# sign and sign
sign_request_item_signer_2.sign(self.signer_2_sign_values)
sign_request_item_signer_3.sign(self.signer_3_sign_values)
self.assertEqual(sign_request_3_roles.state, 'signed', 'The sign request should be signed')
notification = self.env['mail.message'].search([('partner_ids', '=', self.partner_4.id)])
self.assertEqual(notification.subject, 'template_3_roles has been signed')
def test_sign_request_mail_reply_to_exists(self):
sign_request = self.create_sign_request_1_role(self.partner_1, self.env['res.partner'])
responsible_email = sign_request.create_uid.email_formatted
mail = sign_request._message_send_mail(
"body",
record_name=sign_request.reference,
notif_values={
'model_description': 'signature',
'company': self.env.company,
'partner': sign_request.request_item_ids[0].partner_id,
},
mail_values={
'attachment_ids': [],
'subject': sign_request.subject
},
)
self.assertEqual(mail.reply_to, responsible_email, 'reply_to is not set as the responsible email')
def test_sign_send_request_without_order(self):
wizard = Form(self.env['sign.send.request'].with_context(default_template_id=self.template_3_roles.id, sign_directly_without_mail=False))
self.assertEqual([record['mail_sent_order'] for record in wizard.signer_ids._records], [1, 1, 1])
def test_sign_send_request_order_with_order(self):
wizard = Form(self.env['sign.send.request'].with_context(default_template_id=self.template_3_roles.id, sign_directly_without_mail=False))
wizard.set_sign_order = True
self.assertEqual([record['mail_sent_order'] for record in wizard.signer_ids._records], [1, 2, 3])
def test_archived_requests_dont_send_reminders(self):
""" Create a request with a validity period and archive it, jump to the future
where it's not valid anymore, trigger cron reminder and ensure no reminder was created. """
with self.mock_datetime_and_now("2024-05-01"):
validity_date = fields.Date.from_string('2024-05-05')
archived_request = self.create_sign_request_no_item(
signer=self.partner_1,
cc_partners=self.partner_4,
validity=validity_date
)
# This action should set the state to canceled.
archived_request.action_archive()
# Jump to the future and run the cron
with self.mock_datetime_and_now("2024-05-06"):
self.env['sign.request']._cron_reminder()
self.assertTrue(archived_request.state == 'canceled')
def test_send_request_with_default_sign_template(self):
sign_request_record = self.create_sign_request_no_item(signer=self.partner_1, cc_partners=self.partner_4)
activity_type = self.env['mail.activity.type'].create({
'name': 'Signature',
'category': 'sign_request',
})
activity = self.env['mail.activity'].create({
'activity_type_id': activity_type.id,
'res_model_id': self.env['ir.model']._get('sign.request').id,
'res_id': sign_request_record.id,
})
# No default template is set for activity type
action = self.env['sign.template'].with_context(default_activity_id=activity.id).open_sign_send_dialog()
wizard = self.env['sign.send.request'].with_context(action['context']).new()
self.assertFalse(wizard.template_id)
# Default template is set for activity type
activity_type.write({'default_sign_template_id': self.template_1_role.id})
# Login user is not set as responsible(Template is not accessible)
env = self.env(user=self.user_1)
action = env['sign.template'].with_context(default_activity_id=activity.id).open_sign_send_dialog()
wizard = self.env['sign.send.request'].with_context(action['context']).new()
self.assertFalse(wizard.template_id)
# Login user is set as responsible(Template is accessible)
self.template_1_role.write({'user_id': self.user_1.id})
action = self.env['sign.template'].with_context(default_activity_id=activity.id, arj=True).open_sign_send_dialog()
wizard = self.env['sign.send.request'].with_context(action['context']).new()
self.assertEqual(self.template_1_role, wizard.template_id)
@users('admin')
def test_sign_request_notification(self):
"""
Test the sign request notification by creating a user with notification settings
and checking if the notification type is set to 'inbox' in the created sign request.
"""
self.env.user.write({
'name': 'Mitchell Admin',
'email': 'admin@example.com',
'notification_type': 'inbox',
})
with self.mock_mail_gateway():
# Create a sign request
sign_request = self.env['sign.request'].create({
'template_id': self.template_1_role.id,
'reference': self.template_1_role.display_name,
'request_item_ids': [Command.create({
'partner_id': self.partner_1.id,
'role_id': self.role_signer_1.id,
'mail_sent_order': 1,
})],
'subject': 'Test Sign Request',
'message': 'Please sign this document',
})
# Map the sign request items by role
sign_request_items_by_role = {item.role_id: item for item in sign_request.request_item_ids}
sign_request_item_signer_1 = sign_request_items_by_role[self.role_signer_1]
# Ensure the sign request is created with the correct state
self.assertEqual(sign_request.state, 'sent', 'The sign request should be in "sent" state initially')
# Verify that an email was sent to the signer
mail = self.env['mail.mail'].search([
('email_to', '=', formataddr((self.partner_1.name, self.partner_1.email)))
], limit=1)
self.assertTrue(mail, 'The initial sign request email should have been sent to the signer_1')
self.assertSentEmail('"Mitchell Admin" <admin@example.com>', self.partner_1)
self.assertTrue(sign_request_item_signer_1.is_mail_sent, 'An email should be marked as sent for the signer_1')
# Simulate signing the document
sign_request_item_signer_1.sudo().sign(self.single_signer_sign_values)
self.assertEqual(sign_request.state, 'signed', 'The sign request should be signed')
completion_mail_to_user = self.env['mail.mail'].search([
('email_to', '=', formataddr((self.env.user.partner_id.name, self.env.user.partner_id.email))),
('subject', 'ilike', sign_request.reference),
])
self.assertEqual(1, len(completion_mail_to_user), 'Completion email should be sent to the admin user')
completion_mail_to_partner = self.env['mail.mail'].search([
('email_to', '=', formataddr((self.partner_1.name, self.partner_1.email)))
])
self.assertEqual(
2, len(completion_mail_to_partner),
'Two emails should have been sent to the partner: the initial sign request and the completion email'
)
def test_check_state_for_download(self):
"""Test downloading a completed document for signed requests and validation for unsigned requests."""
# Create sign request with 3 roles (signer_1, signer_2, signer_3, cc partners)
sign_request_3_roles = self.create_sign_request_3_roles(
signer_1=self.partner_1,
signer_2=self.partner_2,
signer_3=self.partner_3,
cc_partners=self.partner_4
)
# Map role to corresponding sign request item using a dictionary comprehension
role2sign_request_item = {
sign_request_item.role_id: sign_request_item
for sign_request_item in sign_request_3_roles.request_item_ids
}
# Retrieve individual sign request items for signer 1, 2, and 3
sign_request_item_signer_1 = role2sign_request_item[self.role_signer_1]
sign_request_item_signer_2 = role2sign_request_item[self.role_signer_2]
sign_request_item_signer_3 = role2sign_request_item[self.role_signer_3]
# Get template and sign item IDs
template = sign_request_3_roles.template_id
sign_item_ids = template.sign_item_ids.ids
# Sign the customer sign request item
sign_request_item_signer_1.sign(self.signer_1_sign_values)
# Assertions after signing the signer_1 sign request item
self.assertEqual(sign_request_item_signer_1.state, 'completed', 'The sign.request.item should be completed')
self.assertEqual(sign_request_3_roles.state, 'sent', 'The sign request should be signed')
self.assertEqual(template.sign_item_ids.ids, sign_item_ids, 'The original template should not be changed')
self.assertEqual(
len(sign_request_3_roles.sign_log_ids.filtered(
lambda log: log.action == 'sign' and log.sign_request_item_id == sign_request_item_signer_1
)),
1, 'A log with action="sign" should be created'
)
# Sign the employee sign request item
sign_request_item_signer_2.sign(
self.create_sign_values(sign_request_3_roles.template_id.sign_item_ids, sign_request_item_signer_2.role_id.id)
)
# Assertions after signing the employee sign request item
self.assertEqual(sign_request_item_signer_1.state, 'completed', 'The sign.request.item should be completed')
self.assertEqual(sign_request_item_signer_2.state, 'completed', 'The sign.request.item should be completed')
self.assertEqual(sign_request_item_signer_3.state, 'sent', 'The sign.request.item should be sent')
completed_document = sign_request_3_roles.get_sign_request_documents()
self.assertIsNotNone(completed_document, 'The completed document should be available for download.')
def test_remove_validity_date_of_sign_request(self):
validity_date = fields.Date.to_date(fields.Date.today()) + timedelta(days=1)
sign_request = self.create_sign_request_no_item(signer=self.partner_1, cc_partners=self.partner_4, validity=validity_date)
sign_request.validity = False
self.assertEqual(sign_request.state, 'sent')
def test_expired_shared_sign_requests_are_cleaned_up(self):
""" Tests that the expired sign requests are cleaned when the autovacuum job is called """
with freeze_time("2025-05-16"):
shared_request = self.env["sign.request"].create({
'template_id': self.template_1_role.id,
'reference': self.template_1_role.display_name,
'request_item_ids': [Command.create({
'role_id': self.role_signer_1.id,
})],
'state': 'shared',
'validity': fields.Date.today() + relativedelta(days=3)
})
with freeze_time("2025-05-20"):
with self.enter_registry_test_mode():
autovacuum_job = self.env.ref('base.autovacuum_job')
if autovacuum_job:
autovacuum_job.method_direct_trigger()
self.assertFalse(shared_request.exists(), "The template is not shared anymore.")
def test_sign_request_item_value_cannot_be_changed_after_create(self):
""" Tests that a constant sign item can be created but its value cannot be not modified """
constant_sign_request = self.create_sign_request_with_constant_field(
customer=self.partner_1,
cc_partners=self.partner_4
)
role2sign_request_item = {
sign_request_item.role_id: sign_request_item
for sign_request_item in constant_sign_request.request_item_ids
}
sign_request_item_customer = role2sign_request_item[self.role_1]
with self.assertRaisesRegex(UserError, "Cannot update the value of a read-only sign item"):
sign_request_item_customer.sign(self.create_sign_values(constant_sign_request.template_id.sign_item_ids, sign_request_item_customer.role_id.id))
def test_send_reminder_without_set_validity(self):
with self.mock_datetime_and_now("2025-07-06"):
sign_request = self.create_sign_request_3_roles(signer_1=self.partner_1, signer_2=self.partner_2, signer_3=self.partner_3, cc_partners=self.partner_4)
sign_request.write({'validity': None, 'reminder_enabled': True, 'reminder': 1})
with self.mock_datetime_and_now("2025-07-07"):
self.env['sign.request']._cron_reminder()
def test_signing_order(self):
wizard = Form(
self.env['sign.send.request'].with_context(
default_template_id=self.template_3_roles.id,
sign_directly_without_mail=False,
)
)
wizard.set_sign_order = True
self.assertEqual(
[record['mail_sent_order'] for record in wizard.signer_ids._records],
[1, 2, 3],
)
request = wizard.save()
request.signer_ids[0].mail_sent_order = 3
request.signer_ids[1].mail_sent_order = 2
request.signer_ids[2].mail_sent_order = 1
wizard = Form(request)
wizard.save()
self.assertEqual(
[s.mail_sent_order for s in request.signer_ids],
[3, 2, 1],
)
def test_sign_request_cancel_signed_document(self):
""" Ensure that a fully signed document cannot be canceled. """
sign_request = self.create_sign_request_no_item(signer=self.partner_1, cc_partners=self.partner_4)
sign_request_item = sign_request.request_item_ids[0]
# Sign the document.
sign_request_item.sign(self.signature_fake)
self.assertEqual(sign_request.state, 'signed', 'The sign request should be signed.')
# Attempt to cancel, document should remain signed.
sign_request.cancel()
self.assertEqual(sign_request.state, 'signed', 'A fully signed document should not be canceled.')
@users('admin')
def test_search_need_my_signature(self):
self.env.user.email = "admin@test.com"
sign_request_1 = self.create_sign_request_no_item(signer=self.env.user.partner_id, cc_partners=self.partner_4)
sign_request_2 = self.create_sign_request_no_item(signer=self.partner_2, cc_partners=self.partner_4)
# Search for documents waiting for admin
waiting_for_me = self.env['sign.request'].search([('need_my_signature', '=', True)]).ids
self.assertIn(sign_request_1.id, waiting_for_me, "Document where admin is a signer should be in 'Waiting for me'")
self.assertNotIn(sign_request_2.id, waiting_for_me, "Document where admin is NOT a signer should NOT be in 'Waiting for me'")
def test_origin_offset_translation(self):
sign_request = self.create_sign_request_no_item(signer=self.partner_1, cc_partners=self.partner_4)
offset_box = Mock()
offset_box.lower_left = (-1000, -1000)
offset_box.getWidth.return_value = 1000
offset_box.getHeight.return_value = 1000
with patch('PyPDF2._page.PageObject.add_transformation', create=True) as mock_add, \
patch('PyPDF2._page.PageObject.cropBox', new_callable=PropertyMock, return_value=offset_box):
sign_request.write({'state': 'signed'})
sign_request.template_document_ids.render_document_with_items()
self.assertTrue(mock_add.called, "The origin offset was ignored.")
transformation_matrix = mock_add.call_args[0][-1]
self.assertEqual(transformation_matrix[0], 1)
self.assertEqual(transformation_matrix[3], 1)
self.assertEqual(transformation_matrix[4], -1000)
self.assertEqual(transformation_matrix[5], -1000)