#!/usr/bin/env python3 # © 2021-2026 TechnoLibre (http://www.technolibre.ca) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) """Une copie de site web qui ne sait plus se rendre après la montée. `check_cow_views` cherche la rupture qui ARRÊTE la migration : la forme que l'arch doit avoir a changé. Deux autres la laissent finir sans un mot et ne se voient qu'à l'ouverture de la page. Mesuré sur une migration 12 → 18 réelle : /contact rendait 500 depuis le palier 14 → 15 — quatre paliers de silence. La copie COW de `website.contactus` n'a jamais eu le `t-set='contactus_form_values'` que le module a gagné en chemin, et sur lequel sa vue héritière fait un xpath. Les deux copies appelaient en plus `website.company_description`, gabarit disparu en 18. """ import os import sys import unittest sys.path.append( os.path.normpath(os.path.join(os.path.dirname(__file__), "..")) ) from script.odoo.migration import fix_cow_render as cow # noqa: E402 # La forme réelle, réduite : le module porte l'ancrage, la copie ne l'a # jamais eu, et toutes deux appellent le gabarit disparu. ARCH_MODULE = ( "" "" "" "" "

Contact us

" "
" ) ARCH_COPIE = ( "" "" "

Parlons de votre projet

" "
" "
" ) ARCH_ENFANT = ( "" "" ) class TestWhatAChildAsksFor(unittest.TestCase): def test_it_reads_the_xpath_of_a_child(self): self.assertEqual( cow.anchors_wanted(ARCH_ENFANT), ["//t[@t-set='contactus_form_values']"], ) def test_a_child_without_xpath_asks_for_nothing(self): self.assertEqual( cow.anchors_wanted(""), [] ) def test_an_unparsable_arch_asks_for_nothing_rather_than_crashing(self): self.assertEqual(cow.anchors_wanted("" "" "" "

Ma page

" "
" ) neuf = cow.repair_anchor( copie, ARCH_MODULE, "//t[@t-set='contactus_form_values']" ) hote = etree.fromstring(neuf.encode("utf-8")).xpath( "//t[@t-call='website.layout']" )[0] noms = [e.get("t-set") or e.get("id") for e in hote] self.assertEqual( ["deja_la", "contactus_form_values", "wrap"], noms, neuf ) def test_it_keeps_the_page_written_by_the_customer(self): # C'est tout l'enjeu : réinitialiser la vue rendrait la page # fonctionnelle et effacerait son contenu. neuf = cow.repair_anchor( ARCH_COPIE, ARCH_MODULE, "//t[@t-set='contactus_form_values']" ) self.assertIn("Parlons de votre projet", neuf) def test_it_refuses_when_the_host_element_has_no_twin(self): # Mieux vaut une réparation refusée qu'un bloc posé au hasard au # milieu de la page de quelqu'un. etrangere = "
" self.assertIsNone( cow.repair_anchor( etrangere, ARCH_MODULE, "//t[@t-set='contactus_form_values']" ) ) def test_it_refuses_when_the_module_has_no_anchor_either(self): self.assertIsNone( cow.repair_anchor(ARCH_COPIE, ARCH_COPIE, "//t[@t-set='absent']") ) def test_it_refuses_an_unparsable_arch(self): self.assertIsNone(cow.repair_anchor("", "fr_CA": ""})), ["en_US", "fr_CA"], ) def test_the_query_never_pins_one_language(self): # C'est ICI que le défaut est né : `arch_db->>'en_US'` ramenait # une seule langue, l'audit déclarait la vue saine et /contact # restait en 500 parce que le site rend en fr_CA. La requête doit # ramener l'OBJET entier. requete = cow.arch_expr(jsonb=True) self.assertNotIn("en_US", requete) self.assertNotIn("->>", requete) self.assertIn("arch_db", requete) def test_a_varchar_column_is_wrapped_as_one_nameless_language(self): # Une seule forme en aval : le reste du code n'a pas à savoir si # la version traduit l'arch ou non. requete = cow.arch_expr(jsonb=False) self.assertIn("json_build_object", requete) def test_a_plain_string_is_the_language_less_case(self): self.assertEqual(cow.langs_of(""), {"": ""}) def test_a_non_string_value_is_ignored(self): self.assertEqual( cow.langs_of({"en_US": "", "x": 3}), {"en_US": ""} ) def test_the_xpath_of_a_child_is_read_from_any_language(self): # Les xpath ne sont pas traduits : n'importe quelle langue suffit. self.assertEqual( cow.anchors_wanted(cow.any_lang({"fr_CA": ARCH_ENFANT})), ["//t[@t-set='contactus_form_values']"], ) class TestTheSqlItWrites(unittest.TestCase): def test_it_writes_all_the_languages_in_one_update(self): # Deux UPDATE sur la même vue et le second effacerait le premier ; # un seul portant une langue laisserait l'autre cassée. sql = cow.write_arch_sql( 7, {"en_US": "", "fr_CA": ""}, jsonb=True ) self.assertEqual(1, sql.count("UPDATE"), sql) self.assertIn("en_US", sql) self.assertIn("fr_CA", sql) self.assertIn("", sql) self.assertIn("", sql) self.assertIn("WHERE id = 7", sql) def test_the_varchar_case_writes_the_text_itself(self): sql = cow.write_arch_sql(7, {"": ""}, jsonb=False) self.assertNotIn("jsonb_build_object", sql) self.assertIn("", sql) def test_both_branches_survive_an_apostrophe(self): # La 16 stocke en varchar, la 17 et la 18 en jsonb : les DEUX # chemins écrivent, et n'éprouver que l'un laisse l'autre casser # au premier attribut cité. arch = "" for jsonb, langues in ((True, {"en_US": arch}), (False, {"": arch})): with self.subTest(jsonb=jsonb): sql = cow.write_arch_sql(7, langues, jsonb=jsonb) self.assertIn("$elcow$" + arch + "$elcow$", sql) class TestTheReport(unittest.TestCase): def rapport(self): return { "database": "essai", "jsonb": True, "views": [ { "id": 1228, "key": "website.contactus", "anchors": [{"enfant": 2782, "expr": "//t[@t-set='x']"}], "calls": ["website.company_description"], } ], } def test_a_clean_database_says_so(self): texte = "\n".join( cow.render({"database": "x", "jsonb": True, "views": []}, []) ) self.assertIn("✅", texte) def test_it_names_the_view_the_child_and_the_template(self): texte = "\n".join(cow.render(self.rapport(), [])) self.assertIn("1228", texte) self.assertIn("2782", texte) self.assertIn("website.company_description", texte) def test_it_warns_that_removing_a_call_removes_a_block(self): # Le message passe par `t()` : le comparer à un mot anglais en # dur le fait tomber dès qu'on le traduit. On demande la MÊME # traduction que le code, ce qui éprouve l'avertissement sans # épouser une langue. gestes = [(1228, "k", "t-call", "website.company_description", "SQL")] texte = "\n".join(cow.render(self.rapport(), gestes)) self.assertIn( cow.t("Removing a call removes its block from the page."), texte ) def test_it_stays_silent_about_blocks_when_nothing_is_removed(self): gestes = [(1228, "k", "anchor", "//t", "SQL")] texte = "\n".join(cow.render(self.rapport(), gestes)) self.assertNotIn( cow.t("Removing a call removes its block from the page."), texte ) def test_it_says_when_a_repair_is_out_of_reach(self): gestes = [(1228, "k", "anchor-impossible", "//t", None)] texte = "\n".join(cow.render(self.rapport(), gestes)) self.assertIn("1", texte) self.assertIn("⚠", texte) if __name__ == "__main__": unittest.main()