refactor: remediation plan Fase 0-4 completata

- Fase 0: env var allineate, SYSTEM_PROMPT deduplicato, .gitignore pulito
- Fase 1: GameContext isolato, ciclo DM unificato, provider thread-safe (contextvars)
- Fase 2: DB schema esteso (current_area_id, combat_state, character_items)
- Fase 3: thread-local connection pool + WAL + busy_timeout
- Fase 4: test suite (32 test, 100% pass)
- heuristic_fallback come parser di riserva (non secondo pilota)
This commit is contained in:
truetype committed 2026-06-27 18:00:03 +02:00
1 parent 90f823fdb9
commit cb45172717
16 files changed
+906 -221

No files matched your search

View File
Whitespace-only changes.
+108
View File
@@ -0,0 +1,108 @@
import sys
sys.path.insert(0, "src")
from src.engine.dice import roll, dice_chain, roll_str, validate_die, ALL_DICE
def test_roll_basic():
for _ in range(100):
r = roll("d20")
assert 1 <= r["total"] <= 20
assert r["die"] == "d20"
assert r["count"] == 1
assert r["modifier"] == 0
assert len(r["results"]) == 1
def test_roll_multiple():
for _ in range(50):
r = roll("d6", count=3)
assert len(r["results"]) == 3
assert 3 <= r["total"] <= 18
def test_roll_modifier():
r = roll("d20", modifier=5)
assert r["total"] == r["results"][0] + 5
assert r["modifier"] == 5
def test_roll_crit():
for _ in range(1000):
r = roll("d20")
if r["results"][0] == 20:
assert r["crit"] is True
if r["results"][0] == 1:
assert r["fumble"] is True
def test_roll_d100():
for _ in range(100):
r = roll("d%")
assert 1 <= r["total"] <= 100
def test_roll_dice_chain_up():
res = dice_chain("d3", 2)
assert res["output_die"] == "d5"
assert res["steps"] == 2
assert res["input_die"] == "d3"
def test_dice_chain_overflow():
res = dice_chain("d3", 20)
assert res["output_die"] == "d30"
def test_dice_chain_underflow():
res = dice_chain("d30", -30)
assert res["output_die"] == "d3"
def test_dice_chain_invalid():
res = dice_chain("d9", 1)
assert "error" in res
def test_roll_str_basic():
r = roll_str("2d6+3")
assert r["count"] == 2
assert r["die"] == "d6"
assert r["modifier"] == 3
def test_roll_str_percent():
r = roll_str("d%")
assert r["die"] == "d%"
assert r["count"] == 1
def test_roll_str_negative_mod():
r = roll_str("d20-2")
assert r["modifier"] == -2
def test_validate_die():
assert validate_die("d20") == "d20"
assert validate_die("d%") == "d%"
assert validate_die("d7") == "d7"
assert validate_die("d9") is None
assert validate_die("xyz") is None
def test_all_dice_keys():
assert "d3" in ALL_DICE
assert "d20" in ALL_DICE
assert "d30" in ALL_DICE
assert "d%" in ALL_DICE
assert ALL_DICE["d%"] == 100
def test_roll_zero_faced_die():
r = roll("d0")
assert r["total"] == 0
def test_roll_one_faced_die():
r = roll("d1")
assert r["total"] == 1
+140
View File
@@ -0,0 +1,140 @@
import sys
import json
sys.path.insert(0, "src")
from src.engine.tools import run
from src.engine.db import init_db, create_campaign, create_character, update_character
def _setup_char():
conn = init_db()
camp_id = create_campaign("Test", "Avventura")
char_id = create_character(camp_id, "TestChar", "guerriero", "soldato")
update_character(
char_id,
level=1, str=15, agi=11, sta=14,
per=9, int=8, lck=10,
hp_current=12, hp_max=12, ac=15,
armor_type="light",
)
return camp_id, char_id
def test_roll_dice_tool():
r = run("roll_dice", die="d20")
assert r["success"] is True
assert r["tool"] == "roll-dice"
assert 1 <= r["total"] <= 20
def test_dice_chain_tool():
r = run("dice_chain", die="d3", steps=2)
assert r["success"] is True
assert r["output_die"] == "d5"
def test_unknown_tool():
r = run("nonexistent")
assert r["success"] is False
assert "sconosciuto" in r["message"]
def test_ability_check():
_, char_id = _setup_char()
r = run("ability_check", character_id=char_id, dc=12, modifier=2)
assert r["success"] is True
assert "d20" in r["message"]
def test_saving_throw():
_, char_id = _setup_char()
r = run("saving_throw", character_id=char_id, save_type="Tempra", dc=15)
assert r["success"] is True
assert r["save_type"] == "Tempra"
def test_saving_throw_invalid_type():
_, char_id = _setup_char()
r = run("saving_throw", character_id=char_id, save_type="Forza", dc=15)
assert r["success"] is False
def test_movement():
r = run("movement", distance_m=30, terrain="normale")
assert r["success"] is True
assert r["rounds"] == 3
assert r["speed_m_per_round"] == 12
def test_movement_difficult():
r = run("movement", distance_m=30, terrain="difficile")
assert r["success"] is True
assert r["rounds"] == 5
def test_spellcasting():
_, char_id = _setup_char()
r = run("spellcasting", character_id=char_id, spell_name="Dardo Incantato", spell_check=18, dc=12)
assert r["success"] is True
assert r["result_tier"] in ("minore", "moderato", "maggiore")
def test_spellcasting_failure():
_, char_id = _setup_char()
r = run("spellcasting", character_id=char_id, spell_name="Dardo Incantato", spell_check=5, dc=12)
assert r["success"] is True
assert "fallisce" in r["message"]
def test_update_inventory():
_, char_id = _setup_char()
r = run("update_inventory", character_id=char_id, item="Pozione di Cura")
assert r["success"] is True
assert "Pozione di Cura" in r["message"]
def test_award_xp():
_, char_id = _setup_char()
r = run("award_xp", character_id=char_id, xp=5)
assert r["success"] is True
assert r["xp_gained"] == 5
def test_award_xp_levelup():
camp_id, _ = _setup_char()
# Create a lv0 character (contadino) to test level-up
import src.engine.db as db
lv0_id = db.create_character(camp_id, "Lv0 Hero", "contadino", "contadino")
db.update_character(lv0_id, level=0, xp=0)
r1 = run("award_xp", character_id=lv0_id, xp=10)
assert r1["xp_total"] == 10
assert r1["leveled_up"] is True
def test_dm_override():
r = run("dm_override", command="narrate", params={"text": "Una luce accecante."})
assert r["success"] is True
assert r["tool"] == "dm-override"
def test_dm_override_help():
r = run("dm_override", command="help")
assert r["success"] is True
assert "commands" in r
def test_combat():
_, char_id = _setup_char()
r = run(
"combat",
character_id=char_id,
target_ac=15,
attack_bonus=2,
damage_die="d8",
mighty_deed_die="d3",
luck_mod=0,
armor_type="medium",
)
assert r["success"] is True
assert r["tool"] == "combat"
assert isinstance(r["hit"], bool)