Skip to content

Commit 887e2a8

Browse files
committed
test(cli): ✅ add unit tests for the describe profile <CHECK> subcommand
1 parent f3fb7f3 commit 887e2a8

1 file changed

Lines changed: 144 additions & 0 deletions

File tree

tests/test_cli.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
from click.testing import CliRunner
2020
from pytest import fixture
2121

22+
from rocrate_validator import services
23+
from rocrate_validator.requirements.python import PyFunctionCheck
24+
from rocrate_validator.requirements.shacl.checks import SHACLCheck
25+
2226
from rocrate_validator.utils import log as logging
2327
from rocrate_validator.cli.main import cli
2428
from rocrate_validator.utils.versioning import get_version
@@ -157,3 +161,143 @@ def test_extra_profiles_list(cli_runner: CliRunner, fake_profiles_path: Path):
157161
assert result.exit_code == 0
158162
# assert "Available profiles:" in result.output
159163
assert "Profile A" in result.output # Check for a known extra profile
164+
165+
166+
# Profile used for `profiles describe` tests.
167+
_DESCRIBE_TEST_PROFILE = "ro-crate-1.1"
168+
169+
170+
def _first_visible_check():
171+
"""Return the first non-hidden (Python-backed) check of the test profile."""
172+
profile = services.get_profile(_DESCRIBE_TEST_PROFILE)
173+
for requirement in profile.requirements:
174+
if requirement.hidden:
175+
continue
176+
for check in requirement.get_checks():
177+
if isinstance(check, PyFunctionCheck):
178+
return profile, requirement, check
179+
raise RuntimeError("No Python-backed check found in test profile")
180+
181+
182+
def _first_shacl_check():
183+
"""Return the first non-hidden SHACL-backed check of the test profile."""
184+
profile = services.get_profile(_DESCRIBE_TEST_PROFILE)
185+
for requirement in profile.requirements:
186+
if requirement.hidden:
187+
continue
188+
for check in requirement.get_checks():
189+
if isinstance(check, SHACLCheck):
190+
return profile, requirement, check
191+
raise RuntimeError("No SHACL-backed check found in test profile")
192+
193+
194+
def test_profiles_describe_default(cli_runner: CliRunner):
195+
"""The default describe view (no check id) shows the profile compact view."""
196+
result = cli_runner.invoke(cli, ["profiles", "describe", _DESCRIBE_TEST_PROFILE, "--no-paging"])
197+
assert result.exit_code == 0
198+
assert _DESCRIBE_TEST_PROFILE in result.output
199+
assert "Profile Requirements" in result.output
200+
201+
202+
def test_profiles_describe_verbose(cli_runner: CliRunner):
203+
"""The verbose describe view (no check id) shows individual check identifiers."""
204+
_, _, check = _first_visible_check()
205+
result = cli_runner.invoke(cli, ["profiles", "describe", _DESCRIBE_TEST_PROFILE, "-v", "--no-paging"])
206+
assert result.exit_code == 0
207+
assert check.identifier in result.output
208+
209+
210+
def test_describe_check_relative_id(cli_runner: CliRunner):
211+
"""Resolving a check by '<req#>.<check#>' renders the single-check view."""
212+
_, requirement, check = _first_visible_check()
213+
relative = f"{requirement.order_number}.{check.order_number}"
214+
result = cli_runner.invoke(cli, ["profiles", "describe", _DESCRIBE_TEST_PROFILE, relative, "--no-paging"])
215+
assert result.exit_code == 0, result.output
216+
assert check.identifier in result.output
217+
assert check.severity.name in result.output
218+
219+
220+
def test_describe_check_full_id(cli_runner: CliRunner):
221+
"""Resolving a check by full '<profile>_<req#>.<check#>'."""
222+
_, _, check = _first_visible_check()
223+
result = cli_runner.invoke(cli, ["profiles", "describe", _DESCRIBE_TEST_PROFILE, check.identifier, "--no-paging"])
224+
assert result.exit_code == 0, result.output
225+
assert check.identifier in result.output
226+
227+
228+
def test_describe_check_unknown(cli_runner: CliRunner):
229+
"""An out-of-range check id produces a usage error with a hint."""
230+
result = cli_runner.invoke(cli, ["profiles", "describe", _DESCRIBE_TEST_PROFILE, "99.99", "--no-paging"])
231+
assert result.exit_code == 2
232+
assert "No requirement #99" in result.output
233+
234+
235+
def test_describe_check_bad_format(cli_runner: CliRunner):
236+
"""A non-numeric check id is rejected with a format hint."""
237+
result = cli_runner.invoke(cli, ["profiles", "describe", _DESCRIBE_TEST_PROFILE, "not-an-id", "--no-paging"])
238+
assert result.exit_code == 2
239+
assert "Invalid check identifier" in result.output
240+
241+
242+
def test_describe_check_profile_mismatch(cli_runner: CliRunner):
243+
"""A full id whose prefix doesn't match the requested profile is rejected."""
244+
result = cli_runner.invoke(
245+
cli, ["profiles", "describe", _DESCRIBE_TEST_PROFILE, "some-other-profile_1.1", "--no-paging"]
246+
)
247+
assert result.exit_code == 2
248+
assert "does not belong to profile" in result.output
249+
250+
251+
def test_describe_check_verbose_python(cli_runner: CliRunner):
252+
"""Verbose single-check view on a Python-backed check shows the function source."""
253+
_, requirement, check = _first_visible_check()
254+
relative = f"{requirement.order_number}.{check.order_number}"
255+
result = cli_runner.invoke(
256+
cli, ["profiles", "describe", _DESCRIBE_TEST_PROFILE, relative, "-v", "--no-paging"]
257+
)
258+
assert result.exit_code == 0, result.output
259+
assert "Source" in result.output
260+
# The decorated check function is what gets serialized
261+
assert "@check" in result.output
262+
263+
264+
def test_describe_check_verbose_shacl(cli_runner: CliRunner):
265+
"""Verbose single-check view on a SHACL-backed check shows turtle source."""
266+
_, requirement, check = _first_shacl_check()
267+
relative = f"{requirement.order_number}.{check.order_number}"
268+
result = cli_runner.invoke(
269+
cli, ["profiles", "describe", _DESCRIBE_TEST_PROFILE, relative, "-v", "--no-paging"]
270+
)
271+
assert result.exit_code == 0, result.output
272+
assert "Source" in result.output
273+
# SHACL serialized as turtle should contain a sh: prefix and a NodeShape/PropertyShape declaration
274+
assert "sh:" in result.output
275+
276+
277+
def test_describe_check_verbose_shacl_includes_target(cli_runner: CliRunner):
278+
"""For nested PropertyShape checks, the snippet must include the owning NodeShape's target."""
279+
profile = services.get_profile(_DESCRIBE_TEST_PROFILE)
280+
nested = None
281+
for requirement in profile.requirements:
282+
if requirement.hidden:
283+
continue
284+
for check in requirement.get_checks():
285+
if isinstance(check, SHACLCheck) and getattr(check._shape, "parent", None) is not None:
286+
nested = (requirement, check)
287+
break
288+
if nested:
289+
break
290+
if nested is None:
291+
# No nested PropertyShape check available in this profile; nothing to assert here.
292+
return
293+
requirement, check = nested
294+
relative = f"{requirement.order_number}.{check.order_number}"
295+
result = cli_runner.invoke(
296+
cli, ["profiles", "describe", _DESCRIBE_TEST_PROFILE, relative, "-v", "--no-paging"]
297+
)
298+
assert result.exit_code == 0, result.output
299+
# The snippet must surface the owning shape's target declaration so the user can see
300+
# what the property check applies to.
301+
assert any(t in result.output for t in ("sh:targetClass", "sh:targetNode",
302+
"sh:targetSubjectsOf", "sh:targetObjectsOf",
303+
"sh:target "))

0 commit comments

Comments
 (0)