tests.test_cli module

Conformance of the repomatic.cli package: its lazy table and shared plumbing.

The group imports a command module only when a run needs one of its commands, so the table in repomatic.cli.main is the one place every command is declared. These tests hold that table to the commands it names, and the help sections to the rules that file each command.

tests.test_cli.SECTION_TITLES = ('Project setup', 'Linting & checks', 'Sync', 'Release & versioning', 'GitHub issues & PRs', 'CI & runners', 'Site & docs', 'Forge sampling', 'Tools')

The help sections, in the order the help screen lists them.

tests.test_cli.LOADING_PROBE = 'import importlib\nimport json\nimport sys\n\nfrom repomatic.cli.main import COMMANDS\n\nloaded = sorted(name for name in sys.modules if name.startswith("repomatic.cli."))\nnames = {}\nfor key, spec in COMMANDS.items():\n    module, _, attribute = spec.import_path.rpartition(".")\n    names[key] = getattr(importlib.import_module(module), attribute).name\nprint(json.dumps({"loaded": loaded, "names": names}))\n'

Read the lazy table from a fresh interpreter.

Loading a command through the group renames it after its table key, so only an interpreter that has not loaded any command yet still sees the name Click derived from the callback.

tests.test_cli.test_lazy_table_resolves_from_a_fresh_interpreter()[source]

Each import path names a command whose own name is its table key.

Importing the group loads no command module: a run imports the one module that holds the command it invokes, and the help screen alone imports them all.

tests.test_cli.test_command_module_imports_no_sibling(module)[source]

A command module imports main alone among the CLI modules.

Importing a sibling would load its commands, and all their dependencies, on every run of this module’s commands.

tests.test_cli.test_every_command_is_filed_in_a_section()[source]

No command of the table lands in the catch-all “Other commands” section.

The group itself registers only the help command click-extra adds to every group: click-extra files that one in the default section.

tests.test_cli.test_sync_prefix_decides_the_sync_section(name)[source]

A sync-* command is filed under Sync, and Sync holds nothing else.

tests.test_cli.test_help_lists_the_sections_in_declared_order()[source]

The help screen lists every section, in the order the table declares.

tests.test_cli.test_commands_carry_no_root_option()[source]

A command takes the root options from the group, never declares its own.

The command decorator of click-extra adds them to every command it builds, so a command declared without subcommand shows them all.

tests.test_cli.test_every_output_option_accepts_stdout()[source]

Each --output takes - to print to stdout instead of writing a file.

tests.test_cli.FEATURE_GATE_RE = re.compile('exit_if_disabled\\(ctx, "(?P<key>[\\w.-]+)"\\)')

A command gating itself on a [tool.repomatic] flag, named as a literal.

tests.test_cli.FEATURE_GATES = [('ci.py', 'sync-runner-images.sync'), ('deps.py', 'dependency-graph.update'), ('deps.py', 'vulnerable-deps.sync'), ('github.py', 'labels.sync'), ('github.py', 'setup-guide'), ('release.py', 'binaries.sync'), ('release.py', 'bumpversion.sync'), ('release.py', 'dev-release.sync'), ('release.py', 'github-releases.sync'), ('repo.py', 'gitignore.sync'), ('repo.py', 'mailmap.sync'), ('repo.py', 'metrics.sync')]

Every (module, key) pair the commands gate on.

tests.test_cli.test_feature_gates_are_found()[source]

The scan sees the gates, so the check over them proves something.

tests.test_cli.test_feature_gate_names_a_real_key(module, key)[source]

Every key a command gates on is a [tool.repomatic] setting.

exit_if_disabled reads the flag through Config.is_enabled, which raises on a key it does not know: a typo would break the command on its first run.

tests.test_cli.test_feature_gate_exits_cleanly_when_off(tmp_path, monkeypatch)[source]

A flag switched off in pyproject.toml ends the command with success.

tests.test_cli.test_write_output_closes_the_file_it_opens(tmp_path, monkeypatch)[source]

The stream opened for an --output path is closed once written.

tests.test_cli.test_write_output_leaves_a_captured_stdout_open(monkeypatch)[source]

The live stdout prep_path hands back for - stays open for later writes.

tests.test_cli.test_repo_option_falls_back_in_order(monkeypatch, args, env, detected, expected)[source]

--repo, then $GITHUB_REPOSITORY, then the repository of the checkout.

tests.test_cli.test_repo_option_refuses_to_run_without_a_repository(monkeypatch)[source]
tests.test_cli.test_show_metadata_warns_only_over_content(tmp_path, content, warned)[source]
tests.test_cli.test_group_publishes_the_config_it_resolved(tmp_path, monkeypatch)[source]

Library helpers see the config --config names, not ./pyproject.toml.

The group callback publishes the resolved config for the duration of the command, then clears the slot.

tests.test_cli.RELEASED_CHANGELOG = '# Changelog\n\n## [`1.0.0` (2026-03-04)](https://github.com/kiwi/orchard/compare/v0.9.0...v1.0.0)\n\n- Plant the first row of apple trees.\n'

A changelog whose newest section is released, so changelog opens the next.

tests.test_cli.relocated_changelog(tmp_path, monkeypatch)[source]

A project keeping its changelog away from the default changelog.md.

tests.test_cli.test_changelog_writes_in_place_at_the_configured_location(relocated_changelog)[source]

The new section lands in changelog.location, not in ./changelog.md.

tests.test_cli.test_changelog_in_place_is_idempotent(relocated_changelog)[source]
tests.test_cli.test_changelog_prints_on_dash_and_leaves_the_file(relocated_changelog)[source]