From 84a2700f7f8c18bc2d462c5ed67baaca01ae0173 Mon Sep 17 00:00:00 2001 From: bluefing Date: Tue, 7 Apr 2026 23:33:38 +0100 Subject: [PATCH] feat: extend assert_parameter with attribute checking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds **kwargs support to assert_parameter() so callers can verify parameter attributes (short, long, value, kind, help, pattern, etc.) in addition to checking existence. Backwards compatible — existing calls without kwargs continue to work unchanged. --- src/pytest_just/fixture.py | 25 +++++++++++-- tests/test_assert_parameter.py | 64 ++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 tests/test_assert_parameter.py diff --git a/src/pytest_just/fixture.py b/src/pytest_just/fixture.py index 01149c1..255a496 100644 --- a/src/pytest_just/fixture.py +++ b/src/pytest_just/fixture.py @@ -167,13 +167,34 @@ def assert_depends_on( f"Expected exactly: {sorted(expected_set)}" ) - def assert_parameter(self, recipe: str, parameter: str) -> None: - """Assert that ``recipe`` declares ``parameter``.""" + def assert_parameter(self, recipe: str, parameter: str, **expected: Any) -> None: + """Assert that ``recipe`` declares ``parameter``, optionally checking attributes. + + When called without keyword arguments, checks that the parameter exists. + Each keyword argument is checked against the parameter's dump payload. + + Args: + recipe: Recipe name or namepath. + parameter: Name of the parameter to inspect. + **expected: Attribute name/value pairs to verify (e.g. + ``short="i"``, ``long="interactive"``, ``kind="singular"``). + + Raises: + AssertionError: If the parameter does not exist or any attribute does not match. + """ params = self.parameter_names(recipe) assert parameter in params, ( f"Recipe `{recipe}` is missing parameter `{parameter}`. " f"Available parameters: {params}" ) + if expected: + raw_params: list[dict[str, Any]] = self.parameters(recipe) + p: dict[str, Any] = next(p for p in raw_params if p.get("name") == parameter) + ctx: str = f"{recipe}.{parameter}" + for attr, want in expected.items(): + assert attr in p, f"{ctx}: unexpected attribute `{attr}`" + got = p[attr] + assert got == want, f"{ctx}: expected {attr}={want!r}, got {got!r}" def assert_body_contains(self, recipe: str, text: str) -> None: """Assert that rendered recipe text contains ``text``.""" diff --git a/tests/test_assert_parameter.py b/tests/test_assert_parameter.py new file mode 100644 index 0000000..b144f94 --- /dev/null +++ b/tests/test_assert_parameter.py @@ -0,0 +1,64 @@ +"""Tests for ``JustfileFixture.assert_parameter`` parameter attribute assertions.""" + +from __future__ import annotations + +import shutil +from pathlib import Path + +import pytest + +from pytest_just import JustfileFixture + + +pytestmark = pytest.mark.skipif(shutil.which("just") is None, reason="just binary is required") + + +@pytest.fixture +def jf(tmp_path: Path) -> JustfileFixture: + """Fixture with flag-annotated parameters.""" + (tmp_path / "justfile").write_text( + "[arg('interactive', short = 'i', long, value = 'true')]\n" + "[arg('profile', short = 'p', long)]\n" + "deploy interactive='' profile='' name='':\n" + " @echo {{ interactive }} {{ profile }} {{ name }}\n", + encoding="utf-8", + ) + return JustfileFixture(root=tmp_path) + + +def test_assert_parameter_short(jf: JustfileFixture) -> None: + """Short flag assertion passes when correct.""" + jf.assert_parameter("deploy", "interactive", short="i") + + +def test_assert_parameter_long(jf: JustfileFixture) -> None: + """Long flag assertion passes when correct.""" + jf.assert_parameter("deploy", "profile", long="profile") + + +def test_assert_parameter_value(jf: JustfileFixture) -> None: + """Value flag assertion passes when correct.""" + jf.assert_parameter("deploy", "interactive", value="true") + + +def test_assert_parameter_combined(jf: JustfileFixture) -> None: + """Multiple flag assertions in one call.""" + jf.assert_parameter("deploy", "interactive", short="i", long="interactive", value="true") + + +def test_assert_parameter_missing_param(jf: JustfileFixture) -> None: + """Raises AssertionError for nonexistent parameter.""" + with pytest.raises(AssertionError, match="missing parameter"): + jf.assert_parameter("deploy", "nonexistent", short="x") + + +def test_assert_parameter_wrong_short(jf: JustfileFixture) -> None: + """Raises AssertionError when short flag doesn't match.""" + with pytest.raises(AssertionError, match="expected short"): + jf.assert_parameter("deploy", "interactive", short="x") + + +def test_assert_parameter_unexpected_attr(jf: JustfileFixture) -> None: + """Raises AssertionError for an attribute not in the dump schema.""" + with pytest.raises(AssertionError, match="unexpected attribute"): + jf.assert_parameter("deploy", "interactive", bogus="x")