Skip to content

Commit 352b7f5

Browse files
yarikopticclaude
andcommitted
test: add parametrized CLI tests for single and composite groupings
Extend grouping test coverage from only severity to all grouping values and composite (multi-level) grouping specs: - Parametrize text and JSON CLI tests with 8 specs each: 5 single values (severity, id, validator, standard, dandiset) + 3 composite (severity+id, validator+severity, id+validator) - Parametrize --load and --output tests with single and composite specs - Add _grouping_opts() helper to compose -g args, reused across tests - Assert known issue ID (DANDI.NO_DANDISET_FOUND) in output - Assert nested indentation for composite groupings in text format - Assert nested dict structure for composite groupings in JSON format Co-Authored-By: Claude Code 2.1.81 / Claude Opus 4.6 <noreply@anthropic.com>
1 parent 15db771 commit 352b7f5

File tree

1 file changed

+119
-59
lines changed

1 file changed

+119
-59
lines changed

dandi/cli/tests/test_cmd_validate.py

Lines changed: 119 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -389,13 +389,47 @@ def test_render_text_grouping(grouping: str, capsys: pytest.CaptureFixture) -> N
389389
assert "issue" in captured
390390

391391

392-
@pytest.mark.ai_generated
393-
def test_validate_grouping_severity_cli(simple2_nwb: Path) -> None:
394-
"""Test --grouping=severity via CLI."""
395-
r = CliRunner().invoke(validate, ["--grouping=severity", str(simple2_nwb)])
392+
def _grouping_opts(*groupings: str) -> list[str]:
393+
"""Build CLI args for one or more -g options."""
394+
opts: list[str] = []
395+
for g in groupings:
396+
opts.extend(["-g", g])
397+
return opts
398+
399+
400+
# simple2_nwb always produces at least this ERROR (not inside a dandiset)
401+
_SIMPLE2_EXPECTED_ID = "DANDI.NO_DANDISET_FOUND"
402+
403+
# Single and composite grouping specs for parametrized tests.
404+
# Each entry is a tuple of grouping levels.
405+
_GROUPING_SPECS: list[tuple[str, ...]] = [
406+
("severity",),
407+
("id",),
408+
("validator",),
409+
("standard",),
410+
("dandiset",),
411+
("severity", "id"),
412+
("validator", "severity"),
413+
("id", "validator"),
414+
]
415+
416+
417+
@pytest.mark.ai_generated
418+
@pytest.mark.parametrize("grouping", _GROUPING_SPECS, ids=lambda g: "+".join(g))
419+
def test_validate_grouping_text_cli(
420+
grouping: tuple[str, ...], simple2_nwb: Path
421+
) -> None:
422+
"""Each grouping spec (single or composite) produces section headers and known issues."""
423+
r = CliRunner().invoke(validate, [*_grouping_opts(*grouping), str(simple2_nwb)])
396424
assert r.exit_code != 0
397425
assert "===" in r.output
398-
assert "ERROR" in r.output
426+
# Both known issues must appear somewhere in output
427+
assert _SIMPLE2_EXPECTED_ID in r.output
428+
# Composite groupings must produce nested (indented) headers
429+
if len(grouping) > 1:
430+
lines = r.output.split("\n")
431+
inner = [ln for ln in lines if "===" in ln and ln.startswith(" ")]
432+
assert inner, "composite grouping should produce nested headers"
399433

400434

401435
@pytest.mark.ai_generated
@@ -438,99 +472,125 @@ def test_render_text_multilevel_grouping(capsys: pytest.CaptureFixture) -> None:
438472
_render_text(issues, grouping=("severity", "id"))
439473
captured = capsys.readouterr().out
440474

441-
# Should have nested headers: severity then id
442475
assert "=== WARNING" in captured
443476
assert "=== ERROR" in captured
444477
assert "=== NWBI.check_data_orientation" in captured
445478
assert "=== NWBI.check_missing_unit" in captured
446-
# Nested headers should be indented
447479
lines = captured.split("\n")
448-
# Find inner headers — they should have leading spaces
449480
inner_headers = [ln for ln in lines if "===" in ln and ln.startswith(" ")]
450-
assert len(inner_headers) >= 2 # at least 2 inner group headers
481+
assert len(inner_headers) >= 2
451482

452483

453484
@pytest.mark.ai_generated
454-
def test_validate_multilevel_grouping_text_cli(simple2_nwb: Path) -> None:
455-
"""Test -g severity -g id via CLI produces nested headers."""
456-
r = CliRunner().invoke(validate, ["-g", "severity", "-g", "id", str(simple2_nwb)])
457-
assert r.exit_code != 0
458-
assert "===" in r.output
459-
# Should have nested structure
460-
lines = r.output.split("\n")
461-
inner_headers = [ln for ln in lines if "===" in ln and ln.startswith(" ")]
462-
assert len(inner_headers) >= 1
463-
464-
465-
@pytest.mark.ai_generated
466-
def test_validate_multilevel_grouping_json_cli(simple2_nwb: Path) -> None:
467-
"""Test -g severity -f json_pp via CLI produces nested JSON dict."""
485+
@pytest.mark.parametrize(
486+
"grouping",
487+
[
488+
("path",),
489+
("severity",),
490+
("id",),
491+
("validator",),
492+
("standard",),
493+
("dandiset",),
494+
("severity", "id"),
495+
("validator", "severity"),
496+
],
497+
ids=lambda g: "+".join(g),
498+
)
499+
def test_validate_grouping_json_cli(
500+
grouping: tuple[str, ...], simple2_nwb: Path
501+
) -> None:
502+
"""Each grouping spec produces a (nested) dict in JSON output with known issues."""
468503
r = CliRunner().invoke(
469-
validate, ["-g", "severity", "-f", "json_pp", str(simple2_nwb)]
504+
validate, [*_grouping_opts(*grouping), "-f", "json_pp", str(simple2_nwb)]
470505
)
471506
assert r.exit_code == 1
472507
data = json.loads(r.output)
473-
# With grouping, output should be a dict (not a list)
474508
assert isinstance(data, dict)
475-
# Keys should be severity names
476-
for key in data:
477-
assert key in ("CRITICAL", "ERROR", "WARNING", "HINT", "INFO", "NONE")
478-
# Values should be lists of validation result dicts
479-
for v in data.values():
480-
assert isinstance(v, list)
481-
for rec in v:
482-
assert "id" in rec
509+
assert len(data) >= 1
510+
# For composite groupings, values are nested dicts
511+
if len(grouping) > 1:
512+
for v in data.values():
513+
assert isinstance(v, dict)
514+
else:
515+
for v in data.values():
516+
assert isinstance(v, list)
483517

484518

485519
@pytest.mark.ai_generated
486-
def test_validate_multilevel_grouping_json_two_levels(simple2_nwb: Path) -> None:
487-
"""Test -g severity -g id -f json_pp produces two-level nested JSON."""
520+
def test_validate_grouping_yaml_cli(simple2_nwb: Path) -> None:
521+
"""Grouped YAML output is a dict keyed by grouping values."""
488522
r = CliRunner().invoke(
489-
validate, ["-g", "severity", "-g", "id", "-f", "json_pp", str(simple2_nwb)]
523+
validate, [*_grouping_opts("severity"), "-f", "yaml", str(simple2_nwb)]
490524
)
491525
assert r.exit_code == 1
492-
data = json.loads(r.output)
493-
assert isinstance(data, dict)
494-
# Each value should be a dict (second grouping level)
495-
for severity_key, id_groups in data.items():
496-
assert isinstance(id_groups, dict)
497-
for id_key, results in id_groups.items():
498-
assert isinstance(results, list)
499-
for rec in results:
500-
assert "id" in rec
501-
502-
503-
@pytest.mark.ai_generated
504-
def test_validate_grouping_yaml_cli(simple2_nwb: Path) -> None:
505-
"""Test -g severity -f yaml produces grouped YAML output."""
506-
r = CliRunner().invoke(validate, ["-g", "severity", "-f", "yaml", str(simple2_nwb)])
507-
assert r.exit_code == 1
508526
yaml = ruamel.yaml.YAML(typ="safe")
509527
data = yaml.load(r.output)
510528
assert isinstance(data, dict)
511-
for key in data:
512-
assert key in ("CRITICAL", "ERROR", "WARNING", "HINT", "INFO", "NONE")
529+
assert "ERROR" in data
513530

514531

515532
@pytest.mark.ai_generated
516533
def test_validate_grouping_jsonl_error(simple2_nwb: Path) -> None:
517-
"""Test -g severity -f json_lines gives a UsageError."""
534+
"""Grouping is incompatible with json_lines format."""
518535
r = CliRunner().invoke(
519-
validate, ["-g", "severity", "-f", "json_lines", str(simple2_nwb)]
536+
validate, [*_grouping_opts("severity"), "-f", "json_lines", str(simple2_nwb)]
520537
)
521538
assert r.exit_code != 0
522539
assert "incompatible" in r.output
523540

524541

525542
@pytest.mark.ai_generated
526543
def test_validate_grouping_none_explicit(simple2_nwb: Path) -> None:
527-
"""Test -g none is treated as no grouping."""
528-
r = CliRunner().invoke(validate, ["-g", "none", str(simple2_nwb)])
544+
"""-g none is treated as no grouping."""
545+
r = CliRunner().invoke(validate, [*_grouping_opts("none"), str(simple2_nwb)])
529546
assert r.exit_code != 0
530-
# Should NOT have section headers
531547
assert "===" not in r.output
532548

533549

550+
@pytest.mark.ai_generated
551+
@pytest.mark.parametrize(
552+
"grouping",
553+
[("severity",), ("id", "validator")],
554+
ids=lambda g: "+".join(g),
555+
)
556+
def test_validate_load_with_grouping(
557+
grouping: tuple[str, ...], simple2_nwb: Path, tmp_path: Path
558+
) -> None:
559+
"""--load combined with single or composite --grouping works."""
560+
outfile = tmp_path / "results.jsonl"
561+
CliRunner().invoke(
562+
validate, ["-f", "json_lines", "-o", str(outfile), str(simple2_nwb)]
563+
)
564+
assert outfile.exists()
565+
566+
r = CliRunner().invoke(
567+
validate, ["--load", str(outfile), *_grouping_opts(*grouping)]
568+
)
569+
assert r.exit_code == 1
570+
assert "===" in r.output
571+
assert _SIMPLE2_EXPECTED_ID in r.output
572+
573+
574+
@pytest.mark.ai_generated
575+
@pytest.mark.parametrize(
576+
"grouping",
577+
[("severity",), ("validator", "id")],
578+
ids=lambda g: "+".join(g),
579+
)
580+
def test_validate_grouping_output_file(
581+
grouping: tuple[str, ...], simple2_nwb: Path, tmp_path: Path
582+
) -> None:
583+
"""--grouping with --output writes grouped JSON to file."""
584+
outfile = tmp_path / "grouped.json"
585+
r = CliRunner().invoke(
586+
validate,
587+
[*_grouping_opts(*grouping), "-o", str(outfile), str(simple2_nwb)],
588+
)
589+
assert r.exit_code == 1
590+
data = json.loads(outfile.read_text())
591+
assert isinstance(data, dict)
592+
593+
534594
@pytest.mark.ai_generated
535595
def test_group_results_unit() -> None:
536596
"""Unit test for _group_results with multiple levels."""

0 commit comments

Comments
 (0)