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