[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
"""« Vers quelle version migrer ? » : Entrée doit viser la plus haute.
|
|
|
|
|
|
2026-08-16 07:33:37 -04:00
|
|
|
C'est le choix qu'on fait presque toujours — migrer, c'est aller au bout — et
|
|
|
|
|
il fallait le taper. `click.prompt` reçoit donc un défaut : il affiche
|
|
|
|
|
« [6] » et rend « 6 » sur Entrée, si bien que la réponse suit le chemin
|
|
|
|
|
normal, sans second comportement à tenir d'accord avec le premier.
|
|
|
|
|
|
|
|
|
|
Deux pièges, tous deux vérifiés ici :
|
|
|
|
|
|
|
|
|
|
- SANS `default`, click redemande en silence sur une ligne vide. Une valeur
|
|
|
|
|
par défaut ajoutée sans lui n'aurait jamais été atteinte, et rien ne
|
|
|
|
|
l'aurait signalé.
|
|
|
|
|
- « 6 » est le rang de 18.0 AUJOURD'HUI. Une version de plus au catalogue le
|
|
|
|
|
déplace ; écrit en dur, il ferait choisir 18.0 quand l'écran propose 19.0.
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import ast
|
|
|
|
|
import inspect
|
|
|
|
|
import os
|
|
|
|
|
import unittest
|
|
|
|
|
|
|
|
|
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
|
|
|
|
|
from script.todo.todo_upgrade import TodoUpgrade # noqa: E402
|
|
|
|
|
|
|
|
|
|
|
2026-08-16 07:33:37 -04:00
|
|
|
def prompt_call(source):
|
|
|
|
|
"""Le voisinage de l'appel à click.prompt, où se joue le défaut."""
|
|
|
|
|
start = source.index("click.prompt(")
|
|
|
|
|
return source[start : start + 240]
|
|
|
|
|
|
|
|
|
|
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
class TestTheSource(unittest.TestCase):
|
|
|
|
|
"""Le choix se joue dans une boucle interactive de mille lignes.
|
|
|
|
|
|
2026-08-16 07:33:37 -04:00
|
|
|
On vérifie donc les décisions là où elles s'écrivent : que click reçoive
|
|
|
|
|
un défaut, qu'il soit calculé, et que la question dise la version.
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def source(self):
|
|
|
|
|
return inspect.getsource(TodoUpgrade.execute_odoo_upgrade)
|
|
|
|
|
|
2026-08-16 07:33:37 -04:00
|
|
|
def test_click_receives_a_default(self):
|
|
|
|
|
# Sans lui, Entrée n'atteint aucun code : click redemande, muet.
|
|
|
|
|
window = prompt_call(self.source())
|
|
|
|
|
self.assertIn("default=", window)
|
|
|
|
|
self.assertIn("default_index", window)
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
|
2026-08-16 07:33:37 -04:00
|
|
|
def test_the_default_is_not_written_by_hand(self):
|
|
|
|
|
window = prompt_call(self.source())
|
|
|
|
|
self.assertNotIn('default="6"', window)
|
|
|
|
|
self.assertNotIn("default=6", window)
|
|
|
|
|
|
|
|
|
|
def test_the_rank_is_computed_by_comparison(self):
|
|
|
|
|
# Lire le dernier élément marcherait par chance : l'ordre
|
|
|
|
|
# d'affichage n'est pas une garantie.
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
source = self.source()
|
|
|
|
|
self.assertIn("max(", source)
|
2026-08-16 07:33:37 -04:00
|
|
|
self.assertIn("range(len(lst_odoo_version))", source)
|
|
|
|
|
|
|
|
|
|
def test_an_empty_catalogue_shows_no_default(self):
|
|
|
|
|
# show_default sur une chaîne vide afficherait « [] », qui se lit
|
|
|
|
|
# comme un choix possible.
|
|
|
|
|
self.assertIn(
|
|
|
|
|
"show_default=bool(default_index)", prompt_call(self.source())
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def test_the_question_announces_the_version_not_the_rank(self):
|
|
|
|
|
# click montre « [6] » ; seul le texte dit que c'est 18.0.
|
|
|
|
|
source = self.source()
|
|
|
|
|
self.assertIn("Enter =", source)
|
|
|
|
|
self.assertIn("default_version", source)
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
|
2026-08-16 07:33:37 -04:00
|
|
|
def test_no_special_case_survives(self):
|
|
|
|
|
# click rendant « 6 », le chemin normal suffit. Un cas particulier
|
|
|
|
|
# de plus serait un second comportement à maintenir.
|
|
|
|
|
self.assertNotIn("if not str(status).strip()", self.source())
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
|
|
|
|
|
|
2026-08-16 07:33:37 -04:00
|
|
|
class TestTheRankItself(unittest.TestCase):
|
|
|
|
|
"""Le calcul du défaut, rejoué sur des catalogues réels."""
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
|
2026-08-16 07:33:37 -04:00
|
|
|
def rank(self, lst_version):
|
|
|
|
|
"""Le rang affiché de la plus haute version, comme le code le fait."""
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
lst_odoo_version = [{"prompt_description": v} for v in lst_version]
|
2026-08-16 07:33:37 -04:00
|
|
|
if not lst_odoo_version:
|
|
|
|
|
return None
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
return (
|
|
|
|
|
max(
|
2026-08-16 07:33:37 -04:00
|
|
|
range(len(lst_odoo_version)),
|
|
|
|
|
key=lambda i: float(
|
|
|
|
|
lst_odoo_version[i].get("prompt_description")
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
+ 1
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def test_the_case_reported(self):
|
2026-08-16 07:33:37 -04:00
|
|
|
# Base en 12.0 : la liste proposée va de 13.0 à 18.0, donc [6].
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
self.rank(["13.0", "14.0", "15.0", "16.0", "17.0", "18.0"]), 6
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def test_it_follows_a_new_version(self):
|
|
|
|
|
# LE point : le jour où 19.0 entre au catalogue, le défaut devient
|
|
|
|
|
# [7] sans que personne ne touche au code.
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
self.assertEqual(
|
2026-08-16 07:33:37 -04:00
|
|
|
self.rank(
|
|
|
|
|
["13.0", "14.0", "15.0", "16.0", "17.0", "18.0", "19.0"]
|
|
|
|
|
),
|
|
|
|
|
7,
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
)
|
|
|
|
|
|
2026-08-16 07:33:37 -04:00
|
|
|
def test_an_unsorted_list_points_at_the_highest(self):
|
|
|
|
|
self.assertEqual(self.rank(["18.0", "13.0", "16.0"]), 1)
|
|
|
|
|
|
|
|
|
|
def test_versions_compare_as_numbers(self):
|
|
|
|
|
# « 9.0 » se trie après « 18.0 » en chaînes : la comparaison
|
|
|
|
|
# numérique est ce qui évite de proposer une version antérieure.
|
|
|
|
|
self.assertEqual(self.rank(["9.0", "18.0"]), 2)
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
|
|
|
|
|
def test_a_single_choice(self):
|
2026-08-16 07:33:37 -04:00
|
|
|
self.assertEqual(self.rank(["18.0"]), 1)
|
|
|
|
|
|
|
|
|
|
def test_no_choice_at_all_has_no_rank(self):
|
|
|
|
|
# Une base déjà à la version maximale : pas de défaut plutôt qu'une
|
|
|
|
|
# erreur là où il n'y a rien à faire.
|
|
|
|
|
self.assertIsNone(self.rank([]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestClickReallyReturnsIt(unittest.TestCase):
|
|
|
|
|
"""Ce que click fait vraiment, mesuré et non supposé."""
|
|
|
|
|
|
|
|
|
|
def answer(self, typed, default):
|
|
|
|
|
import io
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
import click
|
|
|
|
|
|
|
|
|
|
original = sys.stdin
|
|
|
|
|
sys.stdin = io.StringIO(typed)
|
|
|
|
|
try:
|
|
|
|
|
return click.prompt(
|
|
|
|
|
"", default=default, show_default=True, prompt_suffix=""
|
|
|
|
|
)
|
|
|
|
|
finally:
|
|
|
|
|
sys.stdin = original
|
|
|
|
|
|
|
|
|
|
def test_enter_returns_the_default(self):
|
|
|
|
|
self.assertEqual(self.answer("\n", "6"), "6")
|
|
|
|
|
|
|
|
|
|
def test_a_typed_value_wins(self):
|
|
|
|
|
self.assertEqual(self.answer("2\n", "6"), "2")
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
|
2026-08-16 07:33:37 -04:00
|
|
|
def test_without_a_default_enter_is_swallowed(self):
|
|
|
|
|
# La raison d'être du `default` : ici click redemande, et c'est la
|
|
|
|
|
# SECONDE ligne qui revient. Entrée n'aurait rien déclenché.
|
|
|
|
|
import io
|
|
|
|
|
import sys
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
|
2026-08-16 07:33:37 -04:00
|
|
|
import click
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
|
2026-08-16 07:33:37 -04:00
|
|
|
original = sys.stdin
|
|
|
|
|
sys.stdin = io.StringIO("\n2\n")
|
|
|
|
|
try:
|
|
|
|
|
self.assertEqual(click.prompt("", prompt_suffix=""), "2")
|
|
|
|
|
finally:
|
|
|
|
|
sys.stdin = original
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
|
|
|
|
|
|
2026-08-16 07:33:37 -04:00
|
|
|
class TestTheModuleStillParses(unittest.TestCase):
|
|
|
|
|
def test_it_parses(self):
|
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
|
|
|
path = os.path.join(REPO, "script", "todo", "todo_upgrade.py")
|
|
|
|
|
with open(path) as handle:
|
|
|
|
|
ast.parse(handle.read())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
unittest.main()
|