|
| 1 | +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. |
| 2 | +"""Guardrail for the CEL Expressions view's ace editor configuration. |
| 3 | +
|
| 4 | +Odoo 19's ``CodeEditor`` OWL component validates its ``mode`` prop against |
| 5 | +a fixed allow-list: |
| 6 | +
|
| 7 | + addons/web/static/src/core/code_editor/code_editor.js |
| 8 | + static MODES = ["javascript", "xml", "qweb", "scss", "python"]; |
| 9 | +
|
| 10 | +A field declaring ``widget="ace" options="{'mode': '<invalid>'}"`` crashes |
| 11 | +the client the moment the editor mounts (the field is only rendered when |
| 12 | +its ``use_cel_*`` toggle is enabled). The original view used |
| 13 | +``'mode': 'text'`` — not in MODES — which is exactly that crash. This test |
| 14 | +parses the view arch and fails if any ace field declares a mode the |
| 15 | +component would reject, so the regression can't silently come back. |
| 16 | +""" |
| 17 | + |
| 18 | +import ast |
| 19 | + |
| 20 | +from lxml import etree |
| 21 | + |
| 22 | +from odoo.tests import TransactionCase, tagged |
| 23 | + |
| 24 | +# Mirror of CodeEditor.MODES (see module docstring). Kept here as a literal |
| 25 | +# because the allow-list lives in JS and isn't importable from Python. |
| 26 | +VALID_ACE_MODES = {"javascript", "xml", "qweb", "scss", "python"} |
| 27 | + |
| 28 | + |
| 29 | +@tagged("post_install", "-at_install") |
| 30 | +class TestCelViewAceMode(TransactionCase): |
| 31 | + """The CEL Expressions form must only use ace modes CodeEditor accepts.""" |
| 32 | + |
| 33 | + def test_cel_ace_fields_use_valid_mode(self): |
| 34 | + view = self.env.ref("spp_approval.view_spp_approval_definition_form_cel") |
| 35 | + arch = etree.fromstring(view.arch) |
| 36 | + |
| 37 | + ace_fields = arch.xpath("//field[@widget='ace']") |
| 38 | + self.assertTrue( |
| 39 | + ace_fields, |
| 40 | + "Expected at least one widget='ace' field in the CEL view — did the view change?", |
| 41 | + ) |
| 42 | + |
| 43 | + for field in ace_fields: |
| 44 | + options = field.get("options") |
| 45 | + self.assertTrue( |
| 46 | + options, |
| 47 | + f"ace field {field.get('name')!r} must declare options with a mode", |
| 48 | + ) |
| 49 | + mode = ast.literal_eval(options).get("mode") |
| 50 | + self.assertIn( |
| 51 | + mode, |
| 52 | + VALID_ACE_MODES, |
| 53 | + f"ace field {field.get('name')!r} uses mode {mode!r}, which " |
| 54 | + f"CodeEditor rejects (valid: {sorted(VALID_ACE_MODES)}). " |
| 55 | + "This crashes the client when the field is rendered.", |
| 56 | + ) |
0 commit comments