Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent empty Round passes and type guarding #84

Merged
merged 3 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading