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:
1 parent
90f823fdb9
commit
cb45172717
16 files changed
+906
-221
No files matched your search
Whitespace-only changes.
@@ -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
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user