From 2f713aed816daf60932d6b63ee96e2072fedbe98 Mon Sep 17 00:00:00 2001 From: Aljes Date: Thu, 24 Sep 2026 12:55:39 +0100 Subject: [PATCH] Document every command, and check that we did `convert` shipped in 0.6.0 with a section in COMMANDS.md that the table of contents never listed. The edit that was supposed to add it matched on "- [`split`](#split)" with backticks; the file writes "- [split](#split)" without them, so neither the replacement nor its fallback fired and nothing said so. The app help string -- the first line of `adata --help` -- named neither `convert` nor `create`. `create` has been missing from it since it was added, which is the clearest sign this needed a test rather than more care. The existing docs suite only walks one direction: everything the docs claim must exist. That catches a documented flag that was renamed, and is blind to a command nobody documented. Three checks now walk the other way, over the Click tree rather than a hardcoded list, so a command added tomorrow is covered without anyone remembering: each visible command needs a section in COMMANDS.md, an entry in its table of contents, and a mention in the app help. Against the previous state they fail three times -- convert in the contents, convert and create in the help -- and nowhere else. Co-Authored-By: Claude Opus 5 --- docs/COMMANDS.md | 1 + src/adata/cli.py | 2 +- tests/test_docs_are_accurate.py | 53 +++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 4cac9b8..82ce5b5 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -18,6 +18,7 @@ Run `adata --help` for the authoritative flag list. - [subset](#subset) - [split](#split) - [concat](#concat) +- [convert](#convert) - [create](#create) - [export](#export) - [import](#import) diff --git a/src/adata/cli.py b/src/adata/cli.py index cd496a5..2501312 100644 --- a/src/adata/cli.py +++ b/src/adata/cli.py @@ -26,7 +26,7 @@ app = typer.Typer( help="Streaming CLI for huge AnnData .h5ad and .zarr stores " - "(view, ls, subset, split, concat, export, import)." + "(view, ls, create, subset, split, concat, convert, export, import)." ) # Use stderr for status/progress to keep stdout clean for data output # force_terminal=True ensures Rich output is visible even in non-TTY environments diff --git a/tests/test_docs_are_accurate.py b/tests/test_docs_are_accurate.py index a3455e3..873514c 100644 --- a/tests/test_docs_are_accurate.py +++ b/tests/test_docs_are_accurate.py @@ -265,3 +265,56 @@ def test_every_relative_link_resolves_inside_the_published_site(page): + "\nUse an absolute https://github.com/... URL for anything outside " "docs/." ) + + +# --------------------------------------------------------------------------- +# every command is documented +# +# The tests above check one direction: that everything the docs claim really +# exists. They say nothing about the reverse, so `convert` shipped in 0.6.0 +# with a section in COMMANDS.md that the table of contents never listed, and +# an app help string that named neither it nor `create`. Both were invisible +# to a suite that only walks from the docs to the code. +# +# Derived from the Click tree rather than a hardcoded list, so a command +# added tomorrow is covered without anyone remembering to add it here. + + +def _visible_commands(): + import typer + + command = typer.main.get_command(app) + return sorted( + name for name, sub in command.commands.items() if not sub.hidden + ) + + +COMMANDS_MD = (REPO / "docs" / "COMMANDS.md").read_text() + + +@pytest.mark.parametrize("name", _visible_commands()) +def test_every_command_has_a_section_in_the_reference(name): + assert f"## `{name}`" in COMMANDS_MD, ( + f"`adata {name}` has no `## \\`{name}\\`` section in docs/COMMANDS.md" + ) + + +@pytest.mark.parametrize("name", _visible_commands()) +def test_every_command_is_in_the_table_of_contents(name): + contents = COMMANDS_MD.split("---", 1)[0] + assert f"](#{name})" in contents, ( + f"`adata {name}` is missing from the table of contents in " + "docs/COMMANDS.md. The link text has no backticks: `- [name](#name)`." + ) + + +@pytest.mark.parametrize("name", _visible_commands()) +def test_every_command_is_named_in_the_app_help(name): + """`adata --help` opens with a list; a command absent from it is hidden + in plain sight, since that line is the first thing anyone reads.""" + import typer + + help_text = typer.main.get_command(app).help or "" + assert name in help_text, ( + f"`{name}` is missing from the app help string in cli.py: {help_text!r}" + )