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

357 lines
19 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from unittest.mock import patch
from freezegun import freeze_time
from json import dumps
from odoo import Command
from odoo.exceptions import AccessError, ValidationError
from odoo.addons.http_routing.tests.common import MockRequest
from odoo.tests import tagged
from odoo.tools import formataddr
from .sign_controller_common import TestSignControllerCommon
@tagged('post_install', '-at_install')
class TestSignController(TestSignControllerCommon):
# test float auto_field display
def test_sign_controller_float(self):
sign_request = self.create_sign_request_no_item(signer=self.partner_1, cc_partners=self.partner_4)
text_type = self.env['sign.item.type'].search([('name', '=', 'Text')])
# the partner_latitude expects 7 zeros of decimal precision
text_type.auto_field = 'partner_latitude'
text_type.model_id = self.env['ir.model']._get('res.partner').id
token_a = self.env["sign.request.item"].search([('sign_request_id', '=', sign_request.id)]).access_token
with MockRequest(sign_request.env):
values = self.SignController.get_document_qweb_context(sign_request.id, token=token_a)
sign_type = next(filter(lambda sign_type: sign_type["name"] == "Text", values.get("rendering_context")["sign_item_types"]))
latitude = sign_type["auto_value"]
self.assertEqual(latitude, 0)
# test auto_field with wrong partner field
def test_sign_controller_dummy_fields(self):
text_type = self.env['sign.item.type'].search([('name', '=', 'Text')])
# we set a dummy field that raises an error
text_type.model_id = self.env['ir.model']._get('res.partner').id
with self.assertRaises(ValidationError):
text_type.auto_field = 'this_is_not_a_partner_field'
# we set a field the demo user does not have access and must not be able to set as auto_field
self.patch(type(self.env['res.partner']).function, 'groups', 'base.group_system')
with self.assertRaises(AccessError):
text_type.with_user(self.user_demo).auto_field = 'function'
# test auto_field with multiple sub steps
def test_sign_controller_multi_step_auto_field(self):
self.partner_1.company_id = self.env.ref('base.main_company')
self.partner_1.company_id.country_id = self.env.ref('base.be').id
sign_request = self.create_sign_request_no_item(signer=self.partner_1, cc_partners=self.partner_4)
text_type = self.env['sign.item.type'].search([('name', '=', 'Text')])
text_type.model_id = self.env['ir.model']._get('res.partner').id
text_type.auto_field = 'company_id.country_id.name'
token_a = self.env["sign.request.item"].search([('sign_request_id', '=', sign_request.id)]).access_token
with MockRequest(sign_request.env):
values = self.SignController.get_document_qweb_context(sign_request.id, token=token_a)
sign_type = next(filter(lambda sign_type: sign_type["name"] == "Text", values.get("rendering_context")["sign_item_types"]))
country = sign_type["auto_value"]
self.assertEqual(country, "Belgium")
def test_sign_request_requires_auth_when_credits_are_available(self):
sign_request = self.create_sign_request_1_role_sms_auth(self.partner_1, self.env['res.partner'])
sign_request_item = sign_request.request_item_ids[0]
self.assertFalse(sign_request_item.signed_without_extra_auth)
self.assertEqual(sign_request_item.role_id.auth_method, 'sms')
sign_vals = self.create_sign_values(sign_request.template_id.sign_item_ids, sign_request_item.role_id.id)
with patch('odoo.addons.iap.models.iap_account.IapAccount.get_credits', lambda self, x: 10):
response = self._json_url_open(
'/sign/sign/%d/%s' % (sign_request.id, sign_request_item.access_token),
data={'signature': sign_vals}
).json()['result']
self.assertFalse(response.get('success'))
self.assertTrue(sign_request_item.state, 'sent')
self.assertFalse(sign_request_item.signed_without_extra_auth)
def test_sign_request_allows_no_auth_when_credits_are_not_available(self):
sign_request = self.create_sign_request_1_role_sms_auth(self.partner_1, self.env['res.partner'])
sign_request_item = sign_request.request_item_ids[0]
self.assertFalse(sign_request_item.signed_without_extra_auth)
self.assertEqual(sign_request_item.role_id.auth_method, 'sms')
sign_vals = self.create_sign_values(sign_request.template_id.sign_item_ids, sign_request_item.role_id.id)
with patch('odoo.addons.iap.models.iap_account.IapAccount.get_credits', lambda self, x: 0):
response = self._json_url_open(
'/sign/sign/%d/%s' % (sign_request.id, sign_request_item.access_token),
data={'signature': sign_vals}
).json()['result']
self.assertTrue(response.get('success'))
self.assertTrue(sign_request_item.state, 'completed')
self.assertTrue(sign_request.state, 'done')
self.assertTrue(sign_request_item.signed_without_extra_auth)
def test_sign_from_mail_no_expiry_params(self):
sign_request = self.create_sign_request_1_role(self.partner_1, self.env['res.partner'])
url = '/sign/document/mail/%s/%s' % (sign_request.id, sign_request.request_item_ids[0].access_token)
response = self.url_open(url)
self.assertEqual(response.status_code, 404)
self.assertTrue('The signature request might have been deleted or modified.' in response.text)
def test_sign_from_mail_link_not_expired(self):
with freeze_time('2020-01-01'):
sign_request = self.create_sign_request_1_role(self.partner_1, self.env['res.partner'])
sign_request_item_id = sign_request.request_item_ids[0]
timestamp = sign_request_item_id._generate_expiry_link_timestamp()
expiry_hash = sign_request_item_id._generate_expiry_signature(sign_request_item_id.id, timestamp)
url = '/sign/document/mail/%(sign_request_id)s/%(access_token)s?timestamp=%(timestamp)s&exp=%(exp)s' % {
'sign_request_id': sign_request.id,
'access_token': sign_request.request_item_ids[0].access_token,
'timestamp': timestamp,
'exp': expiry_hash
}
response = self.url_open(url)
self.assertEqual(response.status_code, 200)
self.assertTrue('/sign/document/%s/%s' % (sign_request.id, sign_request_item_id.access_token) in response.url)
def test_sign_from_mail_with_expired_link(self):
with freeze_time('2020-01-01'):
sign_request = self.create_sign_request_1_role(self.partner_1, self.env['res.partner'])
sign_request_item_id = sign_request.request_item_ids[0]
timestamp = sign_request_item_id._generate_expiry_link_timestamp()
expiry_hash = sign_request_item_id._generate_expiry_signature(sign_request_item_id.id, timestamp)
with freeze_time('2020-01-17'):
url = '/sign/document/mail/%(sign_request_id)s/%(access_token)s?timestamp=%(timestamp)s&exp=%(exp)s' % {
'sign_request_id': sign_request.id,
'access_token': sign_request.request_item_ids[0].access_token,
'timestamp': timestamp,
'exp': expiry_hash
}
response = self.url_open(url)
self.assertEqual(response.status_code, 403)
self.assertTrue('This link has expired' in response.text)
def test_shared_sign_request_without_expiry_params(self):
sign_request = self.create_sign_request_1_role(self.partner_1, self.env['res.partner'])
sign_request.state = 'shared'
sign_request_item_id = sign_request.request_item_ids[0]
url = '/sign/document/mail/%s/%s' % (sign_request.id, sign_request_item_id.access_token)
response = self.url_open(url)
self.assertEqual(response.status_code, 200)
self.assertTrue('/sign/document/%s/%s' % (sign_request.id, sign_request_item_id.access_token) in response.url)
def test_sign_from_resend_expired_link(self):
with freeze_time('2020-01-01'):
sign_request = self.create_sign_request_1_role(self.partner_1, self.env['res.partner'])
sign_request_item_id = sign_request.request_item_ids[0]
timestamp = sign_request_item_id._generate_expiry_link_timestamp()
expiry_hash = sign_request_item_id._generate_expiry_signature(sign_request_item_id.id, timestamp)
url = '/sign/document/mail/%(sign_request_id)s/%(access_token)s?timestamp=%(timestamp)s&exp=%(exp)s' % {
'sign_request_id': sign_request.id,
'access_token': sign_request.request_item_ids[0].access_token,
'timestamp': timestamp,
'exp': expiry_hash
}
response = self.url_open(url)
self.assertEqual(response.status_code, 200)
self.assertTrue('/sign/document/%s/%s' % (sign_request.id, sign_request_item_id.access_token) in response.url)
sign_request_item = {sign_request_item.role_id: sign_request_item for sign_request_item in sign_request.request_item_ids}
sign_request_item_signer_1 = sign_request_item[self.role_signer_1]
sign_request_item_signer_1.sudo().sign(self.single_signer_sign_values)
mail = self.env['mail.mail'].search([('email_to', '=', formataddr((self.partner_1.name, self.partner_1.email)))])
self.assertEqual(len(mail.ids), 2)
with freeze_time('2020-01-17'):
self.start_tour(url, 'sign_resend_expired_link_tour', login='demo')
def test_cancel_request_as_public_user(self):
"""
Test that a public user can cancel a sign request and that a cancellation log is recorded on the partner_id.
"""
sign_request = self.create_sign_request_1_role(self.partner_1, self.env['res.partner'])
sign_request_item = sign_request.request_item_ids[0]
data = {
'params': {
'refusal_reason': 'No.',
},
}
headers = {
'Content-Type': 'application/json'
}
url = '/sign/refuse/%(item_id)s/%(token)s?refusal_reason="test"' % {
'item_id': sign_request.id,
'token': sign_request_item.access_token
}
# Set the environment user as the public user
self.uid = self.public_user
# Send a request to cancel the sign request item
self.authenticate(None, None)
response = self.url_open(url, data=dumps(data), headers=headers)
self.assertEqual(response.status_code, 200)
self.assertEqual(sign_request.state, 'canceled', "Sign request state should be 'canceled'")
self.assertEqual(sign_request_item.state, 'canceled', "Sign request item state should be 'canceled'")
sign_cancel_log = self.env['sign.log'].sudo().search([
('sign_request_id', '=', sign_request.id),
('action', '=', 'cancel')
])
self.assertTrue(sign_cancel_log, "A sign cancel log should be created")
self.assertEqual(sign_cancel_log.request_state, 'canceled',
"Log request state should be 'canceled'")
self.assertEqual(sign_cancel_log.partner_id, self.partner_1,
"Log partner_id should match partner_1")
self.assertEqual(sign_cancel_log.sign_request_item_id.id, sign_request_item.id,
"Log should reference the correct request item")
def test_make_public_user_with_duplicate_contact(self):
"""
Test make_public_user behavior with duplicate contacts.
Ensures that when a user has a duplicate contact with the same email and name,
the sign request associates the correct partner ID which contains user_ids.
"""
user = self.env['res.users'].create({
'name': 'Test User',
'login': 'test_user@example.com',
'email': 'test_user@example.com',
})
contact = user.partner_id
# Duplicate contact and set the user's partner ID to the duplicate contact
duplicate_contact = contact.copy()
user.partner_id = duplicate_contact
self.assertEqual(user.partner_id, duplicate_contact)
self.assertEqual(contact.email, duplicate_contact.email)
# Create a sign request and linked to the user's(Test User) contact
sign_request = self.env['sign.request'].with_context(no_sign_mail=False).create({
'template_id': self.template_no_item.id,
'reference': self.template_no_item.display_name,
'state': 'shared',
'request_item_ids': [Command.create({
'role_id': self.env.ref('sign.sign_item_role_default').id,
})],
})
response = self._json_url_open(
'/sign/send_public/%s/%s' % (sign_request.id, sign_request.access_token),
data={'name': contact.name, 'mail': contact.email}
).json().get('result')
self.assertTrue(response.get('requestID'), 'Request ID should be returned')
sign_request_partner_id = self.env['sign.request'].search(
[('id', '=', response.get('requestID'))]
).request_item_ids.partner_id
self.assertEqual(
duplicate_contact.id, sign_request_partner_id.id,
'The sign request partner ID should match the duplicate contact'
)
self.assertEqual(
sign_request_partner_id.email, contact.email,
'The email of the sign request partner should match the original contact email'
)
self.assertEqual(
sign_request_partner_id.name, duplicate_contact.name,
'The name of the sign request partner should match the original contact name'
)
self.assertEqual(
len(sign_request.request_item_ids), 1,
'There should be exactly one request item in the sign request'
)
def test_get_sign_request_items_with_public_user(self):
""" Test the RPC call to get sign request items as a public user. """
# Create two sign requests. The second one will be returned as 'the next' to be signed.
self.sign_request = self.create_sign_request_1_role(self.partner_1, self.env['res.partner'])
self.next_sign_request = self.create_sign_request_1_role(self.partner_1, self.env['res.partner'])
self.sign_request_item = self.sign_request.request_item_ids[0]
self.authenticate(None, None) # Ensure the current user for the request is public
response = self._json_url_open(
'/sign/sign_request_items',
{
'request_id': self.sign_request.id,
'token': self.sign_request_item.access_token,
'sign_item_id': self.sign_request_item.id,
}
)
self.assertEqual(response.status_code, 200, f"Expected 200 OK, got {response.status_code}")
response_json = response.json() # Get the JSON content of the response
self.assertEqual(response_json['jsonrpc'], '2.0', "Expected JSON-RPC 2.0 response")
self.assertNotIn('error', response_json, f"RPC call returned an error: {response_json.get('error')}")
result = response_json['result']
self.assertIsInstance(result, list, "Result should be a list")
self.assertGreaterEqual(len(result), 1, "Should find at least one sign request item")
# Verify that the found item is the one associated with the test partner's email
found_item_ids = [item['id'] for item in result]
self.assertIn(
self.next_sign_request.request_item_ids[0].id, found_item_ids,
"The 'next' sign request item must be able to be accessed through the public route."
)
def test_sign_request_local(self):
# Make sure the stored signature of an user can't be used by another user locally
# make sure the log user_id is correct
# user_1 is linked to partner1
self.user_1.sign_signature = b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGNgYGAAAAAEAAH2FzhVAAAAAElFTkSuQmCC'
sign_request = self.create_sign_request_1_role(self.partner_1, self.env['res.partner'])
sign_request_item = sign_request.request_item_ids[0]
res = sign_request_item.with_user(self.user_2)._get_user_signature(signature_type='sign_signature')
self.assertFalse(res, "The signature of user1 is not available to user 2")
res = sign_request_item.with_user(self.user_1)._get_user_signature(signature_type='sign_signature')
self.assertTrue(res, "The signature of user1 is available to user1")
sign_vals = self.create_sign_values(sign_request.template_id.sign_item_ids, sign_request_item.role_id.id)
self.authenticate(self.user_2.login, "user_2!user_2")
response = self._json_url_open(
'/sign/sign/%d/%s' % (sign_request.id, sign_request_item.access_token),
data={'signature': sign_vals}
).json()['result']
self.assertTrue(response['success'], "success request")
logs = sign_request.sign_log_ids
create_log = logs.filtered(lambda l: l.action == 'create')
signature_log = logs.filtered(lambda l: l.action == 'sign')
self.assertEqual(create_log.create_uid, self.env.user, "the signature is created by current user")
self.assertEqual(create_log.user_id, self.env.user, "The sign request is created by current user")
self.assertEqual(signature_log.create_uid, self.user_1, "the signature is created by the signing user")
self.assertEqual(signature_log.user_id, self.user_2, "the signature log user_id is the logged in user")
def test_sign_completed_redirection(self):
""" Test that internal users are redirected to the backend when a sign request is completed. """
sign_request = self.create_sign_request_1_role(self.partner_1, self.env['res.partner'])
sign_request_item = sign_request.request_item_ids[0]
sign_vals = self.create_sign_values(sign_request.template_id.sign_item_ids, sign_request_item.role_id.id)
sign_request_item._sign(sign_vals)
url = '/sign/document/%s/%s' % (sign_request.id, sign_request_item.access_token)
# Test redirection for a logged-in user with a sign.group_sign_user
self.authenticate("user_1", "user_1")
response = self.url_open(url)
self.assertEqual(response.status_code, 200)
self.assertTrue(f'/odoo/sign.request/{sign_request.id}/action-sign.Document' in response.url)
# Test no redirection for a public user
self.authenticate(None, None)
response = self.url_open(url)
self.assertEqual(response.status_code, 200)
self.assertFalse('/odoo/sign.request/' in response.url)
# Test no redirection for an internal user without sign.group_sign_user
self.authenticate("user_5", "user_5")
response = self.url_open(url)
self.assertEqual(response.status_code, 200)
self.assertFalse('/odoo/sign.request/' in response.url)