188 lines
5.8 KiB
Python
188 lines
5.8 KiB
Python
# SPDX-FileCopyrightText: 2025 2025
|
|
# SPDX-FileContributor: Gergely Polonkai
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""Tests for the dice roller"""
|
|
|
|
import pytest
|
|
from pytest_mock import MockerFixture
|
|
|
|
from gm_assistant.dice import Die, d666
|
|
|
|
|
|
def test_parse_empty() -> None:
|
|
"""Test parsing an empty string"""
|
|
|
|
with pytest.raises(ValueError):
|
|
Die.parse("")
|
|
|
|
with pytest.raises(ValueError):
|
|
Die.parse(" ")
|
|
|
|
|
|
@pytest.mark.parametrize("descriptor", [pytest.param("12", id="nodie")])
|
|
def test_invalid(descriptor: str) -> None:
|
|
"""Test parsing invalid descriptors"""
|
|
|
|
with pytest.raises(ValueError):
|
|
Die.parse(descriptor)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"descriptor,expected",
|
|
[
|
|
pytest.param("d6", ([6], [], 0), id="single"),
|
|
pytest.param("2d6", ([6, 6], [], 0), id="multiple"),
|
|
pytest.param("d12 + 1", ([12], [], 1), id="modifiedsingle"),
|
|
pytest.param("3d12 - 2", ([12, 12, 12], [], -2), id="modifiedmultiple"),
|
|
pytest.param("2d6+3d8-d4+1", ([6, 6, 8, 8, 8], [4], 1), id="complex"),
|
|
],
|
|
)
|
|
def test_valid(descriptor: str, expected: tuple[list[int], list[int], int]) -> None:
|
|
"""Test parsing valid descriptors"""
|
|
|
|
results = Die.parse(descriptor)
|
|
|
|
assert results == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"descriptor,expected",
|
|
[
|
|
pytest.param("d6", 1, id="single"),
|
|
pytest.param("2d6", 2, id="multiple"),
|
|
pytest.param("d12 + 1", 2, id="modifiedsingle"),
|
|
pytest.param("3d12 - 2", 1, id="modifiedmultiple"),
|
|
pytest.param("2d6+3d8-d4+1", 2, id="complex"),
|
|
],
|
|
)
|
|
def test_minimum(descriptor: str, expected: int) -> None:
|
|
"""Test the ``minimum`` property"""
|
|
|
|
assert Die(descriptor).minimum == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"descriptor,expected",
|
|
[
|
|
pytest.param("d6", 6, id="single"),
|
|
pytest.param("2d6", 12, id="multiple"),
|
|
pytest.param("d12 + 1", 13, id="modifiedsingle"),
|
|
pytest.param("3d12 - 2", 34, id="modifiedmultiple"),
|
|
pytest.param("2d6+3d8-d4+1", 36, id="complex"),
|
|
],
|
|
)
|
|
def test_maximum(descriptor: str, expected: int) -> None:
|
|
"""Test the ``maxiimum`` property"""
|
|
|
|
assert Die(descriptor).maximum == expected
|
|
|
|
|
|
def test_roll_single(mocker: MockerFixture) -> None:
|
|
"""Test rolling a single die"""
|
|
|
|
mocked_randint = mocker.patch("gm_assistant.dice.random.randint", return_value=3)
|
|
|
|
die = Die("d6")
|
|
|
|
assert die.roll() == 3
|
|
mocked_randint.assert_called_once_with(1, 6)
|
|
|
|
|
|
def test_roll_multiple(mocker: MockerFixture) -> None:
|
|
"""Test rolling multiple of the same die"""
|
|
|
|
mocked_randint = mocker.patch("gm_assistant.dice.random.randint", side_effect=[3, 2])
|
|
|
|
die = Die("2d6")
|
|
|
|
assert die.roll() == 5
|
|
assert mocked_randint.call_count == 2
|
|
mocked_randint.assert_has_calls([mocker.call(1, 6), mocker.call(1, 6)])
|
|
|
|
|
|
def test_roll_modified_single(mocker: MockerFixture) -> None:
|
|
"""Test rolling a single dice with a modifier"""
|
|
|
|
mocked_randint = mocker.patch("gm_assistant.dice.random.randint", return_value=3)
|
|
|
|
die = Die("d12+1")
|
|
|
|
assert die.roll() == 4
|
|
mocked_randint.assert_called_once_with(1, 12)
|
|
|
|
|
|
def test_roll_modified_multiple(mocker: MockerFixture) -> None:
|
|
"""Test rolling multiple of the same die with a modifier"""
|
|
|
|
mocked_randint = mocker.patch("gm_assistant.dice.random.randint", side_effect=[3, 2])
|
|
|
|
die = Die("2d12-1")
|
|
|
|
assert die.roll() == 4
|
|
assert mocked_randint.call_count == 2
|
|
mocked_randint.assert_has_calls([mocker.call(1, 12), mocker.call(1, 12)])
|
|
|
|
|
|
def test_roll_complex(mocker: MockerFixture) -> None:
|
|
"""Test rolling a complex die heap"""
|
|
|
|
mocked_randint = mocker.patch("gm_assistant.dice.random.randint", side_effect=[3, 2, 5, 8, 1, 2])
|
|
|
|
die = Die("2d6-1+3d8-d4+2")
|
|
|
|
assert die.roll() == 18
|
|
assert mocked_randint.call_count == 6
|
|
mocked_randint.assert_has_calls(
|
|
[
|
|
mocker.call(1, 6),
|
|
mocker.call(1, 6),
|
|
mocker.call(1, 8),
|
|
mocker.call(1, 8),
|
|
mocker.call(1, 8),
|
|
mocker.call(1, 4),
|
|
]
|
|
)
|
|
|
|
|
|
def test_d666_strict_order(mocker: MockerFixture) -> None:
|
|
"""Test ``d666()`` with strict dice order"""
|
|
|
|
mocked_randint = mocker.patch("gm_assistant.dice.random.randint", side_effect=[2, 4, 6])
|
|
|
|
assert d666(strict_order=True) == [246]
|
|
assert mocked_randint.call_count == 3
|
|
mocked_randint.assert_has_calls([mocker.call(1, 6), mocker.call(1, 6), mocker.call(1, 6)])
|
|
|
|
|
|
def test_d666_any_order(mocker: MockerFixture) -> None:
|
|
"""Test ``d666()`` without strict order"""
|
|
|
|
mocked_randint = mocker.patch("gm_assistant.dice.random.randint", side_effect=[2, 4, 6])
|
|
|
|
assert d666(strict_order=False) == [246, 264, 426, 462, 624, 642]
|
|
assert mocked_randint.call_count == 3
|
|
mocked_randint.assert_has_calls([mocker.call(1, 6), mocker.call(1, 6), mocker.call(1, 6)])
|
|
|
|
|
|
@pytest.mark.parametrize("dice_values", [pytest.param((1, 1, 2)), pytest.param((1, 2, 1)), pytest.param((2, 1, 1))])
|
|
def test_d666_equal_dice(dice_values: tuple[int, int, int], mocker: MockerFixture) -> None:
|
|
"""Test ``d666()`` when some dice are equal"""
|
|
|
|
mocked_randint = mocker.patch("gm_assistant.dice.random.randint", side_effect=dice_values)
|
|
|
|
assert set(d666()) == set((112, 121, 211))
|
|
assert mocked_randint.call_count == 3
|
|
mocked_randint.assert_has_calls([mocker.call(1, 6), mocker.call(1, 6), mocker.call(1, 6)])
|
|
|
|
|
|
@pytest.mark.parametrize("strict_order", (True, False), ids=("strict", "any"))
|
|
def test_d666_all_equal(strict_order: bool, mocker: MockerFixture) -> None:
|
|
"""Test ``d666()`` when all dice are equal"""
|
|
|
|
mocked_randint = mocker.patch("gm_assistant.dice.random.randint", return_value=5)
|
|
|
|
assert d666(strict_order=strict_order) == [555]
|
|
assert mocked_randint.call_count == 3
|
|
mocked_randint.assert_has_calls([mocker.call(1, 6), mocker.call(1, 6), mocker.call(1, 6)])
|