Files
ai-rpg/tests/unit/test_tools_calc_random.py
2026-06-20 19:13:05 +03:00

106 lines
3.2 KiB
Python

"""Tests for the CalcTool and RandomChoiceTool."""
from __future__ import annotations
import pytest
from app.engine.tools.base import ToolContext
from app.engine.tools.game import CalcTool, RandomChoiceTool
class _FakeWorld:
id = "00000000-0000-0000-0000-000000000001"
@pytest.mark.asyncio
async def test_calc_simple_arithmetic():
tool = CalcTool()
ctx = ToolContext(db=None, world=_FakeWorld())
r = await tool.execute({"expression": "2 + 3 * 4"}, ctx)
assert r.ok
assert r.data["result"] == 14
@pytest.mark.asyncio
async def test_calc_with_variables():
tool = CalcTool()
ctx = ToolContext(db=None, world=_FakeWorld())
r = await tool.execute({
"expression": "max(1, player_attack - enemy_armor)",
"variables": {"player_attack": 10, "enemy_armor": 7},
}, ctx)
assert r.ok
assert r.data["result"] == 3
@pytest.mark.asyncio
async def test_calc_dice_notation():
tool = CalcTool()
ctx = ToolContext(db=None, world=_FakeWorld())
r = await tool.execute({"expression": "1d6"}, ctx)
assert r.ok
assert 1 <= r.data["result"] <= 6
assert len(r.data["rolls"]) == 1
@pytest.mark.asyncio
async def test_calc_dice_with_modifier():
tool = CalcTool()
ctx = ToolContext(db=None, world=_FakeWorld())
r = await tool.execute({"expression": "2d6+3"}, ctx)
assert r.ok
assert 5 <= r.data["result"] <= 15
assert len(r.data["rolls"]) == 2
@pytest.mark.asyncio
async def test_calc_rejects_disallowed_chars():
tool = CalcTool()
ctx = ToolContext(db=None, world=_FakeWorld())
r = await tool.execute({"expression": "__import__('os')"}, ctx)
assert not r.ok
assert r.error_code == "validation_error"
@pytest.mark.asyncio
async def test_calc_division_by_zero_returns_error():
tool = CalcTool()
ctx = ToolContext(db=None, world=_FakeWorld())
r = await tool.execute({"expression": "1/0"}, ctx)
assert not r.ok
assert r.error_code == "evaluation_error"
@pytest.mark.asyncio
async def test_random_choice_deterministic_with_same_step():
"""Same world_id + step_id → same choice."""
tool = RandomChoiceTool()
ctx1 = ToolContext(db=None, world=_FakeWorld(), step_id="00000000-0000-0000-0000-000000000010")
ctx2 = ToolContext(db=None, world=_FakeWorld(), step_id="00000000-0000-0000-0000-000000000010")
r1 = await tool.execute({"options": ["a", "b", "c"]}, ctx1)
r2 = await tool.execute({"options": ["a", "b", "c"]}, ctx2)
assert r1.ok and r2.ok
assert r1.data["choice"] == r2.data["choice"]
@pytest.mark.asyncio
async def test_random_choice_weights():
"""Weighted choice should always pick the heavy option when others have weight 0."""
tool = RandomChoiceTool()
ctx = ToolContext(db=None, world=_FakeWorld(), step_id="00000000-0000-0000-0000-000000000020")
for _ in range(10):
r = await tool.execute({
"options": ["always", "never"],
"weights": [1, 0],
}, ctx)
assert r.ok
assert r.data["choice"] == "always"
@pytest.mark.asyncio
async def test_random_choice_requires_two_options():
tool = RandomChoiceTool()
ctx = ToolContext(db=None, world=_FakeWorld())
r = await tool.execute({"options": ["only"]}, ctx)
assert not r.ok