Files
Forge-Engine/tests/game/test_compute_operation.py
williamandClaude Sonnet 5 b2e933f322
Build and deploy / test-python (push) Successful in 11m12s
Build and deploy / test-js (push) Successful in 53s
Build and deploy / lint-python (push) Successful in 3m56s
Build and deploy / lint-js (push) Successful in 3m1s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 3m58s
Reorganisation game/document : renommage screens->game_engine + sous-dossiers game/ dans routes, scripts, static, templates, tests
Prepare la scission a venir entre l'editeur Jeu 2D et le futur editeur
Support de formation (voir docs/plan/PLAN.md), sans toucher a
l'architecture en couches existante :

- screens/ renomme en game_engine/ (nom clair pour le moteur du jeu 2D,
  avant l'arrivee d'un second "moteur" cote document) : ~85 imports
  corriges, contrat import-linter mis a jour, meme forme de couches.
- routes/, scripts/, static/, templates/, tests/ : tout ce qui est
  propre au jeu 2D deplace dans un sous-dossier game/ de chacun
  (routes/game/, static/game/, templates/game/, tests/game/,
  scripts/game/) ; ce qui est partage par le site (auth, onboarding,
  dashboard, uploads, db/) reste a la racine de chaque dossier. Un
  sous-dossier document/ (vide) cree dans chacun pour le futur chantier.
- styles/ volontairement inchange : les 3 fichiers sources sont
  concatenes en un seul static/style.css charge par tout le site,
  scinder leur CONTENU (editeur vs partage) serait un refactor CSS
  distinct, pas un deplacement mecanique.
- Chaine d'export SCORM (publish/build_scorm_package.py) mise a jour en
  profondeur : copie des assets, URLs d'icones relatives a
  static/style.css (qui ne bouge pas), manifeste, wrapper SCORM.
- Deux regressions d'un sweep de renommage anterieur corrigees au passage
  (screens.js/screens/scene-objects incorrectement convertis en
  game_engine.js/game_engine/scene-objects dans des commentaires).
- Effet de bord Windows decouvert et corrige : git mv + Path.write_text
  convertissent des fichiers en CRLF (core.autocrlf=true) - ~189 fichiers
  normalises en LF.
- .eslintrc.json/package.json : uniquement les chemins de glob mis a jour
  (static/game/js/...) ; la preparation eslint-plugin-unicorn du lot 7
  reste volontairement non committee (package-lock.json restaure a la
  version precedente).

Verifications : ruff, mypy --strict (391 fichiers), vulture, bandit,
lint-imports tous verts ; 591/591 tests Python, 276/276 tests JS ;
demarrage serveur + requetes HTTP manuelles confirmant que les assets
deplaces repondent en 200 au nouvel emplacement et 404 a l'ancien.

SKIP=djlint : backlog H021 (styles inline) deja documente comme dette
assumee dans CODE_QUALITY.md section 6, aucun template touche par ce
commit au-dela d'un deplacement de fichier.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 12:27:53 +02:00

119 lines
4.4 KiB
Python

"""Tests unitaires de compute_new_value (game_engine/data_actions/compute_operation.py)
— logique partagée par apply_data_action.py (champ d'objet) et
apply_variable_action.py (variable globale), factorisée pour ne plus être
dupliquée dans les deux fichiers. Verrouille le comportement des 7
opérations historiques ET des 6 opérations ajoutées en Phase 2 (hasard +
opérations mathématiques)."""
import pytest
from game_engine.data_actions.compute_operation import compute_new_value
def test_definir_texte() -> None:
assert compute_new_value("definir_texte", "ancien", "nouveau", False) == "nouveau"
assert compute_new_value("definir_texte", "ancien", "", False) == ""
assert compute_new_value("definir_texte", "ancien", None, False) == ""
def test_definir_nombre_entier_et_decimal() -> None:
assert compute_new_value("definir_nombre", 0, "5", False) == 5
assert isinstance(compute_new_value("definir_nombre", 0, "5", False), int)
assert compute_new_value("definir_nombre", 0, "5.7", True) == 5.7
assert compute_new_value("definir_nombre", 0, "pas un nombre", False) == 0
def test_definir_bool() -> None:
assert compute_new_value("definir_bool_vrai", 0, None, False) == 1
assert compute_new_value("definir_bool_faux", 1, None, False) == 0
def test_basculer_bool() -> None:
assert compute_new_value("basculer_bool", 0, None, False) == 1
assert compute_new_value("basculer_bool", 1, None, False) == 0
assert compute_new_value("basculer_bool", None, None, False) == 1
def test_incrementer_decrementer_entier() -> None:
assert compute_new_value("incrementer", 10, "5", False) == 15
assert compute_new_value("decrementer", 10, "5", False) == 5
assert compute_new_value("incrementer", None, "5", False) == 5
def test_incrementer_decimal_garde_les_decimales() -> None:
result = compute_new_value("incrementer", 10.0, "0.5", True)
assert result == 10.5
assert isinstance(result, float)
def test_incrementer_valeur_invalide_ne_change_rien() -> None:
assert compute_new_value("incrementer", 10, "pas un nombre", False) == 10
def test_operation_inconnue_leve_value_error() -> None:
with pytest.raises(ValueError):
compute_new_value("teleporter", 10, "2", False)
# ---------- Phase 2 — hasard + opérations mathématiques ----------
def test_multiplier() -> None:
assert compute_new_value("multiplier", 10, "3", False) == 30
assert compute_new_value("multiplier", 4.0, "2.5", True) == 10.0
def test_diviser() -> None:
assert compute_new_value("diviser", 10, "2", False) == 5
assert compute_new_value("diviser", 5.0, "2", True) == 2.5
def test_diviser_par_zero_ne_change_rien() -> None:
assert compute_new_value("diviser", 10, "0", False) == 10
def test_modulo() -> None:
assert compute_new_value("modulo", 10, "3", False) == 1
assert compute_new_value("modulo", 10, "0", False) == 10 # garde-fou division par zéro
def test_minimum_releve_la_valeur_si_en_dessous() -> None:
assert compute_new_value("minimum", 3, "10", False) == 10
assert compute_new_value("minimum", 15, "10", False) == 15 # déjà au-dessus : inchangé
def test_maximum_plafonne_la_valeur_si_au_dessus() -> None:
assert compute_new_value("maximum", 15, "10", False) == 10
assert compute_new_value("maximum", 3, "10", False) == 3 # déjà en dessous : inchangé
def test_minimum_maximum_valeur_invalide_ne_change_rien() -> None:
assert compute_new_value("minimum", 5, "pas un nombre", False) == 5
assert compute_new_value("maximum", 5, "", False) == 5
def test_alea_entier_dans_les_bornes() -> None:
for _ in range(50):
result = compute_new_value("alea", 0, "1,6", False)
assert isinstance(result, int)
assert 1 <= result <= 6
def test_alea_decimal_dans_les_bornes() -> None:
for _ in range(50):
result = compute_new_value("alea", 0, "1.5,2.5", True)
assert isinstance(result, float)
assert 1.5 <= result <= 2.5
def test_alea_bornes_inversees_sont_remises_dans_lordre() -> None:
for _ in range(50):
result = compute_new_value("alea", 0, "6,1", False)
assert 1 <= result <= 6
def test_alea_valeur_invalide_retombe_sur_zero() -> None:
assert compute_new_value("alea", 0, "pas un nombre", False) == 0
assert compute_new_value("alea", 0, "", False) == 0
assert compute_new_value("alea", 0, "5", False) == 0 # une seule borne : incomplet