Files
enterprise-addons/test_mail_enterprise/tests/test_mail_compose_message_ai.py
T

152 lines
7.7 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from unittest.mock import patch
from odoo import Command
from odoo.addons.test_mail_enterprise.tests.common_ai import MailCommonAI
from odoo.exceptions import AccessError
from odoo.tests import tagged, users, Form
from odoo.tools import mute_logger
@tagged("post_install", "-at_install")
class TestMailComposeMessageAI(MailCommonAI):
def setUp(self):
super().setUp()
# rename test records to A, B, C and their customer_id to Cust A, Cust B, Cust C
for letter, test_record in zip("ABC", self.test_record | self.test_records):
test_record.write({"name": letter})
test_record.customer_id.write({"name": f"Cust {letter}"})
@mute_logger("odoo.addons.mail.models.mail_mail")
@users("employee")
def test_composer_rendering_ai_prompt_multi(self):
with self._patch_agent_generate_response(
response=["Test response"],
body_html=f"""<div><t t-out="object.name"/> +++ <t t-out="object.customer_id.name"/>{self._wrap_prompt("Test prompt")}</div>""",
):
composer_form = Form(self.env['mail.compose.message'].with_context(
self._get_web_context(
self.test_records,
add_web=True,
default_template_id=self.template.id,
)
))
composer = composer_form.save()
with self.mock_mail_gateway(), self.mock_mail_app():
composer._action_send_mail()
self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record')
for record in self.test_records:
message = record.message_ids[0]
self.assertMailMail(record.customer_id, 'sent',
mail_message=message,
author=self.partner_employee,
email_values={
'email_from': self.partner_employee_2.email_formatted,
'reply_to': 'info@test.example.com',
})
self.assertEqual(message.body, f"<div>{record.name} +++ {record.customer_id.name}<p>Test response</p></div>")
@users("employee")
def test_composer_rendering_ai_prompt_single(self):
with self._patch_agent_generate_response(
response=["Test response"],
body_html=f"""<div><t t-out="object.name"/> +++ <t t-out="object.customer_id.name"/>{self._wrap_prompt("Test prompt")}</div>""",
):
with Form(self.env['mail.compose.message'].with_context(self._get_web_context(
self.test_record,
add_web=True,
))) as composer:
composer.template_id = self.template
self.assertEqual(composer.body, """<div>A +++ Cust A<p>Test response</p></div>""", msg="Evaluated template body_html should be set as the composer body")
@users("employee")
def test_composer_dynamic_content(self):
with self._patch_template_eval_prompts() as get_html_to_eval:
body_html = f"""<div>{self._wrap_prompt('Greet <t t-out="object.name"/> and his buddy <t t-out="object.customer_id.name"/>')}</div>"""
expected_html_to_eval = f"""<div>{self._wrap_prompt('Greet A and his buddy Cust A')}</div>"""
with self._patch_agent_generate_response(
response=["Test response"],
body_html=body_html,
):
composer_form = Form(self.env['mail.compose.message'].with_context(self._get_web_context(
self.test_record,
add_web=True,
default_template_id=self.template.id,
)))
composer_form.save()
self.assertEqual(
get_html_to_eval(),
expected_html_to_eval,
msg="Ensure the dynamic fields are evaluated before passing to the agent.",
)
@users("employee")
def test_composer_dynamic_content_access_error(self):
with self._patch_template_eval_prompts() as get_html_to_eval:
with self._patch_agent_generate_response(
response=["Test response"],
body_html=f"""<div>{self._wrap_prompt('<t t-out="object.name"/> +++ <t t-out="object.phone_number"/>')}</div>""",
):
# clear the user's groups to simulate no access rights to the models used in the template
self.env.user.write({"group_ids": [Command.clear()]})
with self.assertRaises(AccessError):
composer = Form(self.env['mail.compose.message'].with_context(self._get_web_context(
self.test_record,
add_web=True,
default_template_id=self.template.id,
)))
composer.save()
self.assertEqual(
get_html_to_eval(),
None,
msg="There should be no HTML to evaluate if the user has no access to the models used in the template.",
)
@mute_logger("odoo.addons.ai.models.mail_render_mixin")
@users("employee")
def test_composer_missing_composer_should_remove_prompts(self):
with patch.object(self.env.registry["ai.composer"], "_unlink_except_default_rules", lambda self: True):
self.env.ref("ai.ai_mail_template_prompt_evaluator").sudo().unlink()
with self._patch_template_eval_prompts():
with self._patch_agent_generate_response(body_html=f"""<div>Test{self._wrap_prompt('Bla')}</div>"""):
composer_form = Form(self.env['mail.compose.message'].with_context(self._get_web_context(
self.test_record,
add_web=True,
default_template_id=self.template.id,
)))
composer = composer_form.save()
self.assertEqual(composer.body, """<div>Test</div>""", msg="The body should not contain the prompt if the AI composer is missing.")
@mute_logger("odoo.addons.ai.models.mail_render_mixin")
@users("employee")
def test_composer_missing_default_agent_should_remove_prompts(self):
# Temporarily disable the ondelete constraint for testing
with patch.object(self.env.registry["ai.agent"], "_unlink_except_system_agent", lambda self: True):
self.env.ref("ai.ai_default_agent").sudo().unlink()
with self._patch_template_eval_prompts():
with self._patch_agent_generate_response(body_html=f"""<div>Test{self._wrap_prompt('Bla')}</div>"""):
composer_form = Form(self.env['mail.compose.message'].with_context(self._get_web_context(
self.test_record,
add_web=True,
default_template_id=self.template.id,
)))
composer = composer_form.save()
self.assertEqual(composer.body, """<div>Test</div>""", msg="The body should not contain the prompt if the default agent is missing.")
@users("employee")
def test_eval_ai_prompt_with_single_prompt_element(self):
default_agent = self.env.ref("ai.ai_default_agent", raise_if_not_found=True)
with self._patch_agent_generate_response(response=["Hello A and B. I hope you are doing well."]):
html_to_eval = self._wrap_prompt('Greet A and B')
self.assertIn("Hello A and B. I hope you are doing well.",
default_agent._eval_ai_prompts(rendered_html=html_to_eval),
msg="The prompt should be evaluated even if it is the only element in the HTML.",
)