This commit introduces a new optional feature in the chatter. Users can request translation from the Google Cloud Translation API. Documentation: <https://cloud.google.com/translate/docs#docs> Adding the key in settings will instantly enable the feature for all users of the database and consumption is not measured nor limited by Odoo as it is natively supported using the Google Cloud Console. Translation values are stored in the database to reduce cost as much as possible. Therefore, translation values are automatically garbage collected every 2 weeks to prevent database pollution. Task-3338165 closes odoo/odoo#126555 Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
131 lines
5.9 KiB
Python
131 lines
5.9 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import json
|
|
import requests
|
|
from http import HTTPStatus
|
|
from unittest.mock import patch
|
|
|
|
from odoo.tests.common import HttpCase, JsonRpcException, new_test_user, tagged
|
|
from odoo.tools import mute_logger
|
|
|
|
SAMPLE = {
|
|
"text": "<p>Al mal tiempo, buena cara.</p>",
|
|
"src": "es",
|
|
"en": "<p>To bad weather, good face.</p>",
|
|
"fr": "<p>Au mauvais temps, bonne tête.</p>",
|
|
"nl": "<script src='xss-min.js'/><p onclick='XSS()'>Bij slecht weer, goed gezicht.</p>",
|
|
"lang": {
|
|
"fr": "espagnol",
|
|
"en": "Spanish",
|
|
},
|
|
}
|
|
|
|
|
|
def mock_response(fun):
|
|
def wrapper(self, url, data=False, params=False, timeout=5):
|
|
response = requests.Response()
|
|
response.status_code = HTTPStatus.OK
|
|
content = {"data": fun(self, url=url, data=data, params=params)}
|
|
if not content["data"]:
|
|
response.status_code = HTTPStatus.BAD_REQUEST
|
|
content = {"error": {"message": "Mocked Error"}}
|
|
response._content = json.dumps(content).encode()
|
|
return response
|
|
|
|
return wrapper
|
|
|
|
|
|
# Google Cloud Translation Documentation: https://cloud.google.com/translate/docs/reference/api-overview?hl=en
|
|
@tagged("post_install", "-at_install")
|
|
class TestTranslationController(HttpCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.env["res.lang"]._activate_lang("fr_FR")
|
|
cls.env["res.lang"]._activate_lang("en_US")
|
|
cls.env.ref("base.user_admin").write({"lang": "fr_FR"})
|
|
cls.api_key = "VALIDKEY"
|
|
cls.env["ir.config_parameter"].set_param("mail.google_translate_api_key", cls.api_key)
|
|
cls.message = cls.env["mail.message"].create(
|
|
{
|
|
"body": SAMPLE["text"],
|
|
"model": "res.partner",
|
|
"res_id": cls.env.ref("base.user_admin").partner_id.id,
|
|
"message_type": "comment",
|
|
}
|
|
)
|
|
cls.request_count = 0
|
|
|
|
@mock_response
|
|
def _patched_post(self, url, data, params, timeout=5):
|
|
self.request_count += 1
|
|
if f"/v2/detect?key={self.api_key}" in url:
|
|
result = {
|
|
"language": SAMPLE["src"],
|
|
"isReliable": True,
|
|
"confidence": 0.98,
|
|
}
|
|
return {"detections": [[result]]}
|
|
if f"/v2/?key={self.api_key}" in url:
|
|
return {"translations": [{"translatedText": SAMPLE[data.get("target")]}]}
|
|
|
|
def _mock_translation_request(self, data):
|
|
with patch.object(requests, "post", self._patched_post):
|
|
return self.make_jsonrpc_request("/mail/message/translate", data)
|
|
|
|
def test_update_message(self):
|
|
self.authenticate("admin", "admin")
|
|
result = self._mock_translation_request({"message_id": self.message.id})
|
|
self.assertFalse(result.get("error"))
|
|
self.assertEqual(self.env["mail.message.translation"].search_count([]), 1)
|
|
# The translation records should not be discarded if the body did not change.
|
|
self.make_jsonrpc_request(
|
|
"/mail/message/update_content", {"message_id": self.message.id, "body": None, "attachment_ids": []}
|
|
)
|
|
self.assertEqual(self.env["mail.message.translation"].search_count([]), 1)
|
|
self.make_jsonrpc_request(
|
|
"/mail/message/update_content", {"message_id": self.message.id, "body": "update", "attachment_ids": []}
|
|
)
|
|
self.assertFalse(self.env["mail.message.translation"].search_count([]))
|
|
|
|
def test_translation_multi_users(self):
|
|
new_test_user(self.env, "user_test_fr", groups="base.group_user", lang="fr_FR")
|
|
new_test_user(self.env, "user_test_en", groups="base.group_user", lang="en_US")
|
|
for login, target_lang in [("user_test_fr", "fr"), ("user_test_en", "en"), ("admin", "fr")]:
|
|
self.authenticate(login, login)
|
|
result = self._mock_translation_request({"message_id": self.message.id})
|
|
self.assertFalse(result.get("error"))
|
|
self.assertEqual(result["body"], SAMPLE[target_lang])
|
|
self.assertEqual(result["lang_name"], SAMPLE["lang"][target_lang])
|
|
# There is one translation record per target language.
|
|
self.assertEqual(self.env["mail.message.translation"].search_count([]), 2)
|
|
# No API request should be sent if a translation value or source already exists.
|
|
self.assertEqual(self.request_count, 3)
|
|
|
|
def test_invalid_api_key(self):
|
|
self.env["ir.config_parameter"].set_param("mail.google_translate_api_key", "INVALIDKEY")
|
|
self.authenticate("demo", "demo")
|
|
result = self._mock_translation_request({"message_id": self.message.id})
|
|
self.assertNotIn("body", result)
|
|
self.assertNotIn("lang_name", result)
|
|
self.assertTrue(result["error"])
|
|
|
|
def test_html_sanitization(self):
|
|
self.env["res.lang"]._activate_lang("nl_NL")
|
|
new_test_user(self.env, "user_test_nl", groups="base.group_user", lang="nl_NL")
|
|
self.authenticate("user_test_nl", "user_test_nl")
|
|
result = self._mock_translation_request({"message_id": self.message.id})
|
|
self.assertFalse(result.get("error"))
|
|
self.assertHTMLEqual(result["body"], "<p>Bij slecht weer, goed gezicht.</p>")
|
|
translation = self.env["mail.message.translation"].search([])
|
|
self.assertEqual(len(translation), 1)
|
|
self.assertHTMLEqual(translation.body, "<p>Bij slecht weer, goed gezicht.</p>")
|
|
|
|
def test_access_right(self):
|
|
with self.assertRaises(JsonRpcException, msg="odoo.http.SessionExpiredException"):
|
|
self._mock_translation_request({"message_id": self.message.id})
|
|
new_test_user(self.env, "user_test_portal", groups="base.group_portal", lang="fr_FR")
|
|
self.authenticate("user_test_portal", "user_test_portal")
|
|
with self.assertRaises(JsonRpcException, msg="odoo.exceptions.AccessError"), mute_logger("odoo.http"):
|
|
self._mock_translation_request({"message_id": self.message.id})
|