Skip to content

Commit

Permalink
Prevent empty Round passes and type guarding (#84)
Browse files Browse the repository at this point in the history
* Add error for empty passes in a Round constructor.

* Add guards against constructing Pass or Round with wrong archeryutils types.
  • Loading branch information
jatkinson1000 authored Mar 20, 2024
1 parent fd2f524 commit 347e105
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 1 deletion.
9 changes: 9 additions & 0 deletions archeryutils/rounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ class Pass:
def __init__(self, n_arrows: int, target: Target) -> None:
self.n_arrows = abs(n_arrows)
self.target = target
if not isinstance(self.target, Target):
msg = "The target passed to a Pass should be of type Target."
raise TypeError(msg)

@classmethod
def at_target( # noqa: PLR0913
Expand Down Expand Up @@ -202,6 +205,12 @@ def __init__( # noqa: PLR0913
) -> None:
self.name = name
self.passes = list(passes)
if not self.passes:
msg = "passes must contain at least one Pass object but none supplied."
raise ValueError(msg)
if any(not isinstance(x, Pass) for x in self.passes):
msg = "passes in a Round object should be an iterable of Pass objects."
raise TypeError(msg)
self.location = location
self.body = body
self.family = family
Expand Down
33 changes: 32 additions & 1 deletion archeryutils/tests/test_rounds.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Tests for Pass and Round classes."""

from typing import Union
from typing import Iterable, Union

import pytest

Expand Down Expand Up @@ -35,6 +35,14 @@ def test_init(self) -> None:
assert test_pass.target == _target
assert test_pass.n_arrows == 36

def test_invalid_target(self) -> None:
"""Check that Pass raises a TypeError for invalid target."""
with pytest.raises(
TypeError,
match="The target passed to a Pass should be of type Target.",
):
Pass(36, 42) # type: ignore[arg-type]

def test_at_target_constructor(self) -> None:
"""Check indirect initialisation of a Pass with target parameters."""
test_pass = Pass.at_target(36, "5_zone", 122, 50)
Expand Down Expand Up @@ -175,6 +183,29 @@ def test_init_with_iterable_passes(self) -> None:

assert list_.passes == tuple_.passes == iterable_.passes

@pytest.mark.parametrize(
"badpass",
[
pytest.param([]),
pytest.param(()),
],
)
def test_init_with_empty_passes(self, badpass: Iterable) -> None:
"""Check that Round raises a ValueError for empty passes iterable."""
with pytest.raises(
ValueError,
match="passes must contain at least one Pass object but none supplied.",
):
Round("My Round Name", badpass) # type: ignore[arg-type]

def test_init_with_incorrect_type_passes(self) -> None:
"""Check that Round raises a TypeError for passes not containing Pass."""
with pytest.raises(
TypeError,
match="passes in a Round object should be an iterable of Pass objects.",
):
Round("My Round Name", ["a", "b", "c"]) # type: ignore[list-item]

def test_repr(self) -> None:
"""Check Pass string representation."""
test_round = Round("Name", [Pass(36, _target)])
Expand Down

0 comments on commit 347e105

Please sign in to comment.