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

612 lines
31 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from datetime import timedelta
from odoo import Command, fields
from odoo.exceptions import ValidationError, UserError
from odoo.tools import file_open, mute_logger
from odoo.tests import tagged, freeze_time
from odoo.tests.common import TransactionCase, new_test_user
@tagged('post_install', '-at_install')
class TestSignTemplate(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
with file_open('sign/static/demo/sample_contract.pdf', "rb") as f:
cls.pdf_data = base64.b64encode(f.read())
with file_open('mail/tests/discuss/files/test_AES.pdf', "rb") as f:
cls.AES_pdf_data = base64.b64encode(f.read())
with file_open('mail/tests/discuss/files/test_unicode.pdf', "rb") as f:
cls.unicode_pdf_data = base64.b64encode(f.read())
cls.test_user = new_test_user(cls.env,
"test_user_1",
email="test_user_1@nowhere.com",
password="test_user_1",
tz="UTC",
groups='sign.group_sign_user')
def test_create_update_copy_unlink_template(self):
# create
res = self.env['sign.template'].with_user(self.test_user).create_from_attachment_data(
attachment_data_list=[{'name': 'sample_contract.pdf', 'datas': self.pdf_data}])
sign_template_id, name = res.get('id', 0), res.get('name', '')
sign_template = self.env['sign.template'].with_user(self.test_user).browse(sign_template_id)
document_id = sign_template.document_ids[0].id
document = self.env['sign.document'].browse(document_id)
self.assertTrue(sign_template.exists(), 'The template should be created')
self.assertTrue(sign_template.document_ids.exists(), 'The template should be created')
self.assertEqual(name, 'sample_contract.pdf', 'The name of the template should be sample_contract.pdf')
# update
result = sign_template.update_from_pdfviewer(sign_items={'-1': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': self.env.ref('sign.sign_item_role_default').id,
'page': 1,
'posX': 0.273,
'posY': 0.158,
'template_id': sign_template_id,
'document_id': document_id,
'width': 0.150,
'height': 0.015,
'transaction_id': -1,
}, '-2': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': self.env.ref('sign.sign_item_role_default').id,
'page': 1,
'posX': 0.273,
'posY': 0.158,
'template_id': sign_template_id,
'document_id': document_id,
'width': 0.150,
'height': 0.015,
'transaction_id': -2,
}}, name='')
self.assertEqual(len(sign_template.sign_item_ids), 2, 'The template should have 2 sign.item')
self.assertTrue(result.get('-1', 0) > 0 and result.get('-2', 0) > 0, 'An id mapping should be returned')
self.assertEqual(set(sign_template.sign_item_ids.ids), set(result.values()), 'An id mapping should be returned')
self.assertEqual(document.name, 'sample_contract.pdf', 'The name of the document should be sample_contract.pdf')
sign_template.update_from_pdfviewer(deleted_sign_item_ids=[sign_template.sign_item_ids[0].id], name='sample_contract2.pdf', document_id=document_id)
self.assertEqual(len(sign_template.sign_item_ids), 1, 'The template should have 1 sign.item')
self.assertEqual(document.name, 'sample_contract2.pdf', 'The name of the document should be sample_contract2.pdf')
# copy
copy_name = sign_template._get_copy_name(sign_template.name)
self.assertNotEqual(sign_template.name, copy_name, 'The copy name should not equal to the original one')
sign_template_copy = sign_template.copy()
self.assertEqual(sign_template_copy.name, copy_name, 'The name of the copied template should be decided by the _get_copy_name method')
self.assertEqual(len(sign_template.sign_item_ids), 1, 'The copied template should have 1 sign.item')
# unlink
sign_item = sign_template.sign_item_ids[0]
sign_document = sign_template.document_ids[0]
sign_template.unlink()
self.assertFalse(sign_item.exists(), 'The sign_item should be deleted')
self.assertFalse(sign_document.exists(), 'The document should be deleted')
def test_update_from_pdfviewer_bad_internet(self):
# create
res = self.env['sign.template'].with_user(self.test_user).create_from_attachment_data(
attachment_data_list=[{'name': 'sample_contract.pdf', 'datas': self.pdf_data}])
sign_template_id = res.get('id', 0)
sign_template = self.env['sign.template'].with_user(self.test_user).browse(sign_template_id)
document_id = sign_template.document_ids[0].id
# add new sign items
# A client creates a new item1(-1)
result1 = sign_template.update_from_pdfviewer(sign_items={'-1': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': self.env.ref('sign.sign_item_role_default').id,
'page': 1,
'posX': 0.273,
'posY': 0.058,
'width': 0.150,
'height': 0.015,
'transaction_id': -1,
'document_id': document_id,
}})
item1_id = result1.get('-1', 0)
self.assertEqual(len(sign_template.sign_item_ids), 1, 'The template should have 1 sign.item')
self.assertEqual(set(result1.keys()), set(['-1']), 'An id mapping should be returned')
self.assertEqual(set(sign_template.sign_item_ids.ids), set([item1_id]), 'An id mapping should be returned')
self.assertEqual(self.env['sign.item'].browse(item1_id).posY, 0.058, 'The poxY of item1 should be 0.058')
# result1 is received by client
# The client creates new item2(-2) and item3(-3), and updates the posY of item1
result2 = sign_template.update_from_pdfviewer(sign_items={str(item1_id): {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': self.env.ref('sign.sign_item_role_default').id,
'page': 1,
'posX': 0.273,
'posY': 0.158,
'width': 0.150,
'height': 0.015,
'transaction_id': 0,
'document_id': document_id,
}, '-2': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': self.env.ref('sign.sign_item_role_default').id,
'page': 1,
'posX': 0.273,
'posY': 0.258,
'width': 0.150,
'height': 0.015,
'transaction_id': -2,
'document_id': document_id,
}, '-3': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': self.env.ref('sign.sign_item_role_default').id,
'page': 1,
'posX': 0.273,
'posY': 0.358,
'width': 0.150,
'height': 0.015,
'transaction_id': -3,
'document_id': document_id,
}})
self.assertEqual(set(result2.keys()), set(['-2', '-3']), 'An id mapping should be returned')
self.assertEqual(set(sign_template.sign_item_ids.ids), set(list(result2.values()) + [item1_id]), 'An id mapping should be returned')
self.assertEqual(self.env['sign.item'].browse(item1_id).posY, 0.158, 'The poxY of item1 should be 0.158')
# Result2 is not received by the client / the client sends another rpc call before it receive the result2
# The client removes the item3(-3) and create a new item4(-4) and update the posY of item2(-2)
result3 = sign_template.update_from_pdfviewer(sign_items={str(item1_id): {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': self.env.ref('sign.sign_item_role_default').id,
'page': 1,
'posX': 0.273,
'posY': 0.158,
'width': 0.150,
'height': 0.015,
'transaction_id': 0,
'document_id': document_id,
}, '-2': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': self.env.ref('sign.sign_item_role_default').id,
'page': 1,
'posX': 0.273,
'posY': 0.298,
'width': 0.150,
'height': 0.015,
'transaction_id': -2,
'document_id': document_id,
}, '-4': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': self.env.ref('sign.sign_item_role_default').id,
'page': 1,
'posX': 0.273,
'posY': 0.458,
'width': 0.150,
'height': 0.015,
'transaction_id': -4,
'document_id': document_id,
}}, deleted_sign_item_ids=[-3])
item2_id = result3.get('-2', 0)
self.assertEqual(set(result3.keys()), set(['-2', '-4']), 'An id mapping should be returned')
self.assertEqual(set(sign_template.sign_item_ids.ids), set(list(result3.values()) + [item1_id]), 'An id mapping should be returned')
self.assertEqual(self.env['sign.item'].browse(item1_id).posY, 0.158, 'The poxY of item1 should be 0.158')
self.assertEqual(self.env['sign.item'].browse(item2_id).posY, 0.298, 'The poxY of item2 should be 0.298')
def test_sign_item_has_proper_type(self):
"""Tests that a sign item can only be set as read-only if it has certain types."""
res = self.env['sign.template'].with_user(self.test_user).create_from_attachment_data(
attachment_data_list=[{'name': 'sample_contract.pdf', 'datas': self.pdf_data}])
sign_template_id = res.get('id', 0)
sign_template = self.env['sign.template'].with_user(self.test_user).browse(sign_template_id)
document_id = sign_template.document_ids[0].id
for item_type in ('signature', 'initial', 'radio', 'checkbox', 'selection'):
with self.subTest(f'Create sign item of type {item_type}'):
with self.assertRaisesRegex(ValidationError, "Read-only can only be applied to items of the following types: 'Text', 'Name', 'Email', 'Phone', 'Company', 'Multiline', 'Date', 'Strikethrough'"):
type_id = self.env["sign.item.type"].create({
'name': item_type,
'item_type': item_type
})
self.env["sign.item"].create({
'template_id': sign_template_id,
'document_id': document_id,
'type_id': type_id.id,
'name': 'employee_id.name',
'required': False,
'constant': True,
'responsible_id': self.env.ref('sign.sign_item_role_default').id,
'page': 1,
'posX': 0.273,
'posY': 0.458,
'width': 0.150,
'height': 0.015,
'transaction_id': -4,
})
def test_sign_item_model_name(self):
""" Make sure that we can't save a model on sign.template that is incompatible with the sign items """
# Use models that are available in the sign application
model_sr = self.env['ir.model']._get('sign.request')
model_cron = self.env['ir.model']._get('ir.cron')
model_partner = self.env['ir.model']._get('res.partner')
type_request = self.env["sign.item.type"].create({
'name': "SR field",
'item_type': 'text',
'model_id': model_sr.id,
'auto_field': 'reference',
})
type_partner = self.env["sign.item.type"].create({
'name': "Partner field",
'item_type': 'text',
'model_id': model_partner.id,
'auto_field': 'partner_latitude',
})
type_cron = self.env["sign.item.type"].create({
'name': "Cron field",
'item_type': 'text',
'model_id': model_cron.id,
'auto_field': 'interval_type',
})
res = self.env['sign.template'].with_user(self.test_user).create_from_attachment_data(
attachment_data_list=[{'name': 'sample_contract.pdf', 'datas': self.pdf_data}])
sign_template_id = res.get('id', 0)
sign_template = self.env['sign.template'].with_user(self.test_user).browse(sign_template_id)
document_id = sign_template.document_ids[0].id
for sign_type in [type_request, type_partner, type_cron]:
self.env["sign.item"].create({
'template_id': sign_template_id,
'document_id': document_id,
'type_id': sign_type.id,
'name': 'employee_id.name',
'required': False,
'constant': True,
'responsible_id': self.env.ref('sign.sign_item_role_default').id,
'page': 1,
'posX': 0.273,
'posY': 0.458,
'width': 0.150,
'height': 0.015,
'transaction_id': -4,
})
with self.assertRaises(UserError):
sign_template.model_id = type_request.id
with self.assertRaises(UserError):
sign_template.model_id = type_cron.id
@mute_logger('odoo.addons.sign.utils.pdf_handling', 'pypdf._reader')
def test_invalid_pdf_upload(self):
""" Make sure that uploading invalid/encrypted PDF upload should raise the error. """
for fname, data in [('test_AES.pdf', self.AES_pdf_data), ('test_unicode.pdf', self.unicode_pdf_data)]:
with self.subTest(fname=fname):
with self.assertRaises(ValidationError):
self.env['sign.template'].with_user(self.test_user).create_from_attachment_data(
attachment_data_list=[{'name': fname, 'datas': data}]
)
@mute_logger('odoo.addons.sign.utils.pdf_handling', 'pypdf._reader')
def test_invalid_pdf_add_in_document(self):
""" Make sure that adding invalid/encrypted PDF in Document should raise the error. """
# create a valid template first
res = self.env['sign.template'].with_user(self.test_user).create_from_attachment_data(
attachment_data_list=[{'name': 'sample_contract.pdf', 'datas': self.pdf_data}]
)
template_id = self.env['sign.template'].with_user(self.test_user).browse(res.get('id', 0))
self.assertEqual(len(template_id.document_ids), 1)
for fname, data in [('test_AES.pdf', self.AES_pdf_data), ('test_unicode.pdf', self.unicode_pdf_data)]:
with self.subTest(fname=fname):
with self.assertRaises(ValidationError):
template_id.update_from_attachment_data(attachment_data_list=[{'name': fname, 'datas': data}])
def test_update_document_preserves_sign_items(self):
""" Ensure that updating a document in a sign template creates a new template and preserves sign items. """
# Create a template with one document and one sign item
res = self.env['sign.template'].with_user(self.test_user).create_from_attachment_data(
attachment_data_list=[{'name': 'sample_contract.pdf', 'datas': self.pdf_data}]
)
template_id = res.get('id', 0)
template = self.env['sign.template'].with_user(self.test_user).browse(template_id)
document = template.document_ids[0]
self.env['sign.item'].create({
'template_id': template.id,
'document_id': document.id,
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': self.env.ref('sign.sign_item_role_default').id,
'transaction_id': -1, 'page': 1,
'posX': 0.273, 'posY': 0.158, 'width': 0.150, 'height': 0.015,
})
self.assertEqual(len(template.sign_item_ids), 1, 'Template should have 1 sign item before replacement')
# Prepare new document data, call update_document, get the new template and perform the checks.
new_attachment_data = {'name': 'new_contract.pdf', 'datas': self.pdf_data}
action = template.update_document(document.id, new_attachment_data)
new_template_id = action['params']['id']
new_template = self.env['sign.template'].browse(new_template_id)
self.assertTrue(any(doc.name == 'new_contract.pdf' for doc in new_template.document_ids), 'New template should have the new document')
self.assertEqual(len(new_template.sign_item_ids), 1, 'Sign items should be preserved in the new template')
def _get_signer_and_item_gc_context(self, sign_template):
template_item_ids = self.env['sign.item'].search([('template_id', '=', sign_template.id)])
all_signer_ids = template_item_ids.mapped('responsible_id.id')
active_signer_ids = template_item_ids.filtered(lambda item: item.page > -1).mapped('responsible_id.id')
template_active_signer_ids = sign_template.sign_item_ids.mapped('responsible_id.id')
signers_without_item_ids = self.env['sign.item.role'].search([
('id', 'in', all_signer_ids),
('id', 'not in', active_signer_ids)
]).ids
non_active_item_ids = template_item_ids.filtered(lambda item: item.page < 0).ids
return all_signer_ids, template_active_signer_ids, signers_without_item_ids, non_active_item_ids
def test_gc_removes_orphan_roles_and_dummy_items(self):
sign_template_id = self.env['sign.template'].with_user(self.test_user).create_from_attachment_data(
attachment_data_list=[{'name': 'sample_contract.pdf', 'datas': self.pdf_data}])['id']
sign_template = self.env['sign.template'].with_user(self.test_user).browse(sign_template_id)
document_id = sign_template.document_ids[0].id
signers_with_items = []
signers_without_item = []
signers_with_items.append(sign_template.create_item_and_role(document_id, 'Signer1'))
signer1_item_id = sign_template.update_from_pdfviewer(sign_items={'-1': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'Signer1',
'required': True,
'responsible_id': signers_with_items[-1],
'page': 1,
'posX': 0.273,
'posY': 0.058,
'width': 0.150,
'height': 0.015,
'transaction_id': -1,
'document_id': document_id,
}})['-1']
signers_with_items.append(sign_template.create_item_and_role(document_id, 'Signer2'))
signer2_item_id = sign_template.update_from_pdfviewer(sign_items={'-2': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'Signer2',
'required': True,
'responsible_id': signers_with_items[-1],
'page': 1,
'posX': 0.273,
'posY': 0.058,
'width': 0.150,
'height': 0.015,
'transaction_id': -1,
'document_id': document_id,
}})['-2']
signers_without_item.append(sign_template.create_item_and_role(document_id, 'Signer3'))
signers_without_item.append(sign_template.create_item_and_role(document_id, 'Signer4'))
all_signer_ids, template_active_signer_ids, signers_without_item_ids, non_active_item_ids \
= self._get_signer_and_item_gc_context(sign_template)
self.assertTrue(set(signers_with_items + signers_without_item).issubset(set(all_signer_ids)),
"The signers created is not in the database.")
self.assertEqual(set(signers_with_items), set(template_active_signer_ids),
"The loaded signers in template should only be the signers with active sign item.")
self.assertEqual(set(signers_without_item), set(signers_without_item_ids),
"The signers without active item in the database is not equal to the one created.")
self.assertEqual({signer1_item_id, signer2_item_id}, set(sign_template.sign_item_ids.ids),
"The loaded sign items in the template should only be the active items.")
self.assertEqual(len(non_active_item_ids), 4,
"The number of non-active sign items should equal to 4.")
garbage_collecting_time = fields.Datetime.now() + timedelta(days=1, minutes=1)
with freeze_time(garbage_collecting_time):
sign_template._gc_sign_items()
self.env.flush_all()
self.cr.flush()
all_signer_ids, template_active_signer_ids, signers_without_item_ids, non_active_item_ids \
= self._get_signer_and_item_gc_context(sign_template)
self.assertTrue(set(signers_with_items).issubset(set(all_signer_ids)),
"Signers with active item should be a subset of all signers in the database after autovacuum.")
self.assertFalse(set(signers_without_item).issubset(set(all_signer_ids)),
"Signers without active item should not be a subset of all signers in the database after autovacuum.")
self.assertEqual(set(signers_with_items), set(template_active_signer_ids),
"The loaded signers in template should still be the same after autovacuum.")
self.assertEqual(len(signers_without_item_ids), 0,
"Signers without active item should be deleted after autovacuum.")
self.assertEqual({signer1_item_id, signer2_item_id}, set(sign_template.sign_item_ids.ids),
"The loaded sign items in the template should only be the active items.")
self.assertEqual(len(non_active_item_ids), 0,
"The number of non-active sign items should equal to 0 after autovacuum.")
def test_template_sign_tour(self):
"""
Check that users who do not have the Sign admin group can access
the template returned by `trigger_template_tour`, as access errors
related to sign items happened during the copy of the original template.
"""
original_template = self.env.ref('sign.template_sign_tour')
original_template.sign_request_ids.unlink()
original_template.document_ids.sign_item_ids.unlink()
# check user with limited Sign rights can access the sample template
self.env['sign.template'].with_user(self.test_user).trigger_template_tour()
role = self.env['sign.item.role'].create({'name': 'test'})
document_id = original_template.document_ids[0].id
original_template.update_from_pdfviewer(sign_items={'-1': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': role.id,
'page': 1,
'posX': 0.273,
'posY': 0.158,
'document_id': document_id,
'width': 0.150,
'height': 0.015,
'transaction_id': -1,
}})
self.assertTrue(original_template.sign_item_ids)
self.env['sign.request'].create({
'template_id': original_template.id,
'reference': 'Test Sign Request',
'request_item_ids': [Command.create({
'partner_id': self.env.user.partner_id.id,
'role_id': role.id,
})],
})
self.assertTrue(original_template.has_sign_requests)
# check user with limited Sign rights can access the sample template after items
# have been added to it and a sign request has been created.
res = self.env['sign.template'].with_user(self.test_user).trigger_template_tour()
template_id = res['params']['id']
template = self.env['sign.template'].browse(template_id)
# check existing sign items have not been copied to avoid access errors
self.assertFalse(template.sign_item_ids)
def test_copy_template_gives_own_roles(self):
"""
When duplicating a template, its sign items should get their own copy of any non-default role,
so that editing a role on one template doesn't leak to the other.
"""
sign_template_id = self.env['sign.template'].with_user(self.test_user).create_from_attachment_data(
attachment_data_list=[{'name': 'sample_contract.pdf', 'datas': self.pdf_data}])['id']
sign_template = self.env['sign.template'].with_user(self.test_user).browse(sign_template_id)
sign_template.with_user(self.test_user).update_from_attachment_data(
attachment_data_list=[{'name': 'sample_contract.pdf', 'datas': self.pdf_data}])
document_id = sign_template.document_ids[0].id
document_id_2 = sign_template.document_ids[1].id
custom_role_id = sign_template.create_item_and_role(document_id, 'Signer')
custom_role_id_2 = sign_template.create_item_and_role(document_id, 'Signer 2')
default_role = self.env.ref('sign.sign_item_role_default')
sign_template.update_from_pdfviewer(sign_items={'-1': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': custom_role_id,
'page': 1,
'posX': 0.273,
'posY': 0.158,
'document_id': document_id,
'width': 0.150,
'height': 0.015,
'transaction_id': -1,
},
'-2': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': default_role.id,
'page': 1,
'posX': 0.273,
'posY': 0.258,
'document_id': document_id,
'width': 0.150,
'height': 0.015,
'transaction_id': -2,
},
'-3': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': custom_role_id_2,
'page': 1,
'posX': 0.273,
'posY': 0.158,
'document_id': document_id_2,
'width': 0.150,
'height': 0.015,
'transaction_id': -1,
},
'-4': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': custom_role_id,
'page': 1,
'posX': 0.273,
'posY': 0.158,
'document_id': document_id_2,
'width': 0.150,
'height': 0.015,
'transaction_id': -1,
},
'-5': {
'type_id': self.env.ref('sign.sign_item_type_text').id,
'name': 'employee_id.name',
'required': True,
'responsible_id': custom_role_id_2,
'page': 1,
'posX': 0.273,
'posY': 0.158,
'document_id': document_id,
'width': 0.150,
'height': 0.015,
'transaction_id': -1,
}})
sign_template_copy = sign_template.copy()
copy_custom_item = sign_template_copy.sign_item_ids.filtered(lambda i: not i.responsible_id.default)
copy_default_item = sign_template_copy.sign_item_ids.filtered(lambda i: i.responsible_id.default)
self.assertNotEqual(copy_custom_item.responsible_id, self.env['sign.item.role'].browse(custom_role_id),
'The duplicated template should own a new copy of the non-default role')
copied_roles = copy_custom_item.responsible_id
self.assertNotIn(custom_role_id, copied_roles.ids,
'The duplicated template should own a new copy of the non-default role')
self.assertNotIn(custom_role_id_2, copied_roles.ids,
'The duplicated template should own a new copy of the non-default role')
self.assertEqual(copy_default_item.responsible_id, default_role, 'The default role should be shared, not copied')
# editing the role on the copy must not leak to the original template
partner = self.env['res.partner'].create({'name': 'Test Partner'})
copied_roles.assign_to = partner
roles_original = sign_template.sign_item_ids.filtered(lambda i: not i.responsible_id.default).responsible_id
self.assertFalse(roles_original.assign_to,
"Editing the copied role should not affect the original template's role")
self.assertEqual(len(copied_roles), len(roles_original),
'A role shared across documents should be copied once, not once per document')
def test_get_selection_ids_from_value_order(self):
"""
Test that get_selection_ids_from_value returns IDs in the correct order and creates missing ones.
"""
SignItemOption = self.env["sign.item.option"]
options = ["Beta", "Alpha", "Gamma"]
ids = SignItemOption.get_selection_ids_from_value(options)
recs = SignItemOption.browse(ids)
self.assertEqual(len(recs), 3)
self.assertEqual(recs[0].value, "Beta")
self.assertEqual(recs[1].value, "Alpha")
self.assertEqual(recs[2].value, "Gamma")
more_options = ["Delta", "Alpha", "Beta"]
ids2 = SignItemOption.get_selection_ids_from_value(more_options)
recs2 = SignItemOption.browse(ids2)
self.assertEqual(len(recs2), 3)
self.assertEqual(recs2[0].value, "Delta")
self.assertEqual(recs2[1].value, "Alpha")
self.assertEqual(recs2[2].value, "Beta")