summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-cli/tests/test_manpage.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-14 22:20:15 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-14 22:20:15 +0200
commit422647e7898b8df67cd11f8fb060aec23dec84aa (patch)
tree7ecca0e8df1bc8a7d26cf4b471bd44906dd9cfd1 /packages/meshbay-cli/tests/test_manpage.py
parent28c88908b88c58cba4de08710f6bb268ede02d57 (diff)
downloadmeshbay-422647e7898b8df67cd11f8fb060aec23dec84aa.tar.gz
feat(cli): meshbay-cli, a command-line client for members
Signs in to the hub, lists the account's groups and their folders, and downloads and uploads files directly with each group's node, end-to-end encrypted. With no argument it opens a two-pane interactive mode (Textual). Session and device keys are kept encrypted under the system's key storage, or a 0600 key file where there is none; node keys are pinned at first contact; a new machine links as a device. Packaged on its own on Linux (deb and rpm, on meshbay-common's venv, with a man page generated from the parser) and frozen next to meshbay-node in the full Windows installer, not the light one, with a self-test the build runs. client/keys.py ports keyderive.js and is LGPL like it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-cli/tests/test_manpage.py')
-rw-r--r--packages/meshbay-cli/tests/test_manpage.py70
1 files changed, 70 insertions, 0 deletions
diff --git a/packages/meshbay-cli/tests/test_manpage.py b/packages/meshbay-cli/tests/test_manpage.py
new file mode 100644
index 0000000..ebd71c8
--- /dev/null
+++ b/packages/meshbay-cli/tests/test_manpage.py
@@ -0,0 +1,70 @@
+"""
+The man page is generated from the parser; these keep the generator honest.
+
+An option added to an action must reach the page, every exit code must be
+explained, and troff must read the result without a warning.
+"""
+
+import argparse
+import os
+import shutil
+import subprocess
+
+import pytest
+from meshbay_cli import __main__ as cli
+from meshbay_cli import errors, manpage
+
+
+def _section(page: str, start: str) -> str:
+ return page.split(start + "\n", 1)[1].split("\n.S", 1)[0]
+
+
+def _entry(option: str) -> str:
+ """How an option opens its entry: `.TP`, then the option in bold."""
+ return f"\\fB{manpage._roff(option)}\\fR"
+
+
+def test_every_action_and_option_is_in_its_section():
+ page = manpage.render()
+ parser = cli.build_parser()
+ glob = _section(page, ".SH GLOBAL OPTIONS")
+ for a in parser._actions:
+ for option in a.option_strings:
+ assert _entry(option) in glob, f"global {option} missing"
+ sub = next(a for a in parser._actions if isinstance(a, argparse._SubParsersAction))
+ assert list(sub.choices) == list(cli.ACTIONS)
+ for name, sp in sub.choices.items():
+ own = _section(page, f".SS {name}")
+ for a in sp._actions:
+ if isinstance(a, argparse._HelpAction):
+ continue
+ if a.option_strings and _entry(a.option_strings[0]) in glob:
+ continue # a global one, listed once
+ if a.option_strings:
+ for option in a.option_strings:
+ assert _entry(option) in own, f"{name} {option} missing"
+ elif a.choices:
+ for choice in a.choices:
+ assert _entry(choice) in own, f"{name} {choice} missing"
+ else:
+ meta = a.metavar or a.dest.upper()
+ assert f"\\fI{manpage._roff(meta)}\\fR" in own, f"{name} {meta} missing"
+
+
+def test_every_exit_code_is_explained():
+ codes = {v for k, v in vars(errors).items() if k.isupper() and isinstance(v, int)}
+ assert codes | {0} == set(manpage.EXIT_CODES)
+
+
+def test_names_and_text_cannot_break_the_page():
+ assert manpage._roff(".hidden") == "\\&.hidden"
+ assert manpage._roff("a\\b — c-d") == "a\\eb \\[u2014] c\\-d"
+
+
+@pytest.mark.skipif(not shutil.which("groff"), reason="groff not installed")
+def test_troff_reads_it_without_a_warning(tmp_path):
+ page = tmp_path / "meshbay-cli.1"
+ page.write_text(manpage.render(), encoding="ascii") # nothing past ASCII left
+ r = subprocess.run(["groff", "-man", "-Tutf8", "-ww", "-z", str(page)],
+ capture_output=True, text=True, env={**os.environ, "LC_ALL": "C"})
+ assert r.returncode == 0 and not r.stderr, r.stderr