mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2026-03-24 14:06:31 +01:00
149 lines
5.5 KiB
Python
149 lines
5.5 KiB
Python
"""Tests for RandomService.
|
|
|
|
Covers seed validation, seed derivation, and RNG creation via the service.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from code.exceptions import InvalidSeedError
|
|
from code.web.services.random_service import RandomService
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Fixtures #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
@pytest.fixture
|
|
def service() -> RandomService:
|
|
return RandomService()
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# validate_seed #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
class TestValidateSeed:
|
|
def test_none_is_valid(self, service):
|
|
service.validate_seed(None) # should not raise
|
|
|
|
def test_positive_int_is_valid(self, service):
|
|
service.validate_seed(0)
|
|
service.validate_seed(1)
|
|
service.validate_seed(12345)
|
|
service.validate_seed((1 << 63) - 1)
|
|
|
|
def test_nonempty_string_is_valid(self, service):
|
|
service.validate_seed("dragons")
|
|
service.validate_seed("1")
|
|
service.validate_seed(" ") # whitespace-only is allowed by service
|
|
|
|
def test_negative_int_raises(self, service):
|
|
with pytest.raises(InvalidSeedError) as exc_info:
|
|
service.validate_seed(-1)
|
|
assert exc_info.value.code == "INVALID_SEED"
|
|
|
|
def test_empty_string_raises(self, service):
|
|
with pytest.raises(InvalidSeedError):
|
|
service.validate_seed("")
|
|
|
|
def test_bool_raises(self, service):
|
|
with pytest.raises(InvalidSeedError):
|
|
service.validate_seed(True)
|
|
with pytest.raises(InvalidSeedError):
|
|
service.validate_seed(False)
|
|
|
|
def test_list_raises(self, service):
|
|
with pytest.raises(InvalidSeedError):
|
|
service.validate_seed([1, 2, 3]) # type: ignore
|
|
|
|
def test_dict_raises(self, service):
|
|
with pytest.raises(InvalidSeedError):
|
|
service.validate_seed({"seed": 1}) # type: ignore
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# derive_seed #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
class TestDeriveSeed:
|
|
def test_int_seed_stable(self, service):
|
|
assert service.derive_seed(42) == 42
|
|
assert service.derive_seed(0) == 0
|
|
|
|
def test_string_seed_stable(self, service):
|
|
s1 = service.derive_seed("test-seed")
|
|
s2 = service.derive_seed("test-seed")
|
|
assert s1 == s2
|
|
|
|
def test_string_seed_known_value(self, service):
|
|
# Same expected value as test_random_util.py
|
|
assert service.derive_seed("test-seed") == 6214070892065607348
|
|
|
|
def test_different_strings_differ(self, service):
|
|
a = service.derive_seed("alpha")
|
|
b = service.derive_seed("beta")
|
|
assert a != b
|
|
|
|
def test_negative_int_raises(self, service):
|
|
with pytest.raises(InvalidSeedError):
|
|
service.derive_seed(-5)
|
|
|
|
def test_result_within_63_bits(self, service):
|
|
for seed in [0, 1, 99999, "hello", "dragons"]:
|
|
result = service.derive_seed(seed)
|
|
assert 0 <= result < (1 << 63)
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# create_rng #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
class TestCreateRng:
|
|
def test_seeded_rng_is_deterministic(self, service):
|
|
rng1 = service.create_rng(seed=12345)
|
|
rng2 = service.create_rng(seed=12345)
|
|
seq1 = [rng1.random() for _ in range(5)]
|
|
seq2 = [rng2.random() for _ in range(5)]
|
|
assert seq1 == seq2
|
|
|
|
def test_string_seeded_rng_is_deterministic(self, service):
|
|
rng1 = service.create_rng(seed="dragons")
|
|
rng2 = service.create_rng(seed="dragons")
|
|
seq1 = [rng1.random() for _ in range(5)]
|
|
seq2 = [rng2.random() for _ in range(5)]
|
|
assert seq1 == seq2
|
|
|
|
def test_different_seeds_produce_different_streams(self, service):
|
|
rng1 = service.create_rng(seed=1)
|
|
rng2 = service.create_rng(seed=2)
|
|
seq1 = [rng1.random() for _ in range(10)]
|
|
seq2 = [rng2.random() for _ in range(10)]
|
|
assert seq1 != seq2
|
|
|
|
def test_unseeded_returns_independent_instance(self, service):
|
|
rng1 = service.create_rng()
|
|
rng2 = service.create_rng()
|
|
assert rng1 is not rng2
|
|
|
|
def test_seeded_rng_is_independent_object(self, service):
|
|
rng1 = service.create_rng(seed=42)
|
|
rng2 = service.create_rng(seed=42)
|
|
assert rng1 is not rng2
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# generate_seed #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
class TestGenerateSeed:
|
|
def test_returns_int_in_range(self, service):
|
|
for _ in range(10):
|
|
s = service.generate_seed()
|
|
assert isinstance(s, int)
|
|
assert 0 <= s < (1 << 63)
|
|
|
|
def test_seeds_are_not_all_identical(self, service):
|
|
seeds = {service.generate_seed() for _ in range(5)}
|
|
# With 63-bit entropy collisions are astronomically unlikely
|
|
assert len(seeds) > 1
|