From 757e76add73df154aa6818841654a165cc50fd51 Mon Sep 17 00:00:00 2001 From: didericis Date: Mon, 11 May 2026 16:35:55 -0400 Subject: [PATCH] test(cli): tighten and relocate --format=json validation test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the --format=json-requires-dry-run check out of the integration suite (it doesn't need Docker — argparse fails before any backend runs) and tighten the assertion: previously asserted only that exit code was nonzero, so any unrelated breakage (manifest resolution failure, bad agent name, etc.) silently passed. Now asserts stderr contains the actual flag-conflict message. Co-Authored-By: Claude Opus 4.7 --- tests/integration/test_dry_run_plan.py | 28 -------------- tests/unit/test_cli_start_format.py | 53 ++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 28 deletions(-) create mode 100644 tests/unit/test_cli_start_format.py diff --git a/tests/integration/test_dry_run_plan.py b/tests/integration/test_dry_run_plan.py index 7850afe..1672faa 100644 --- a/tests/integration/test_dry_run_plan.py +++ b/tests/integration/test_dry_run_plan.py @@ -78,34 +78,6 @@ class TestDryRunPlan(unittest.TestCase): import shutil shutil.rmtree(work_dir, ignore_errors=True) - def test_json_format_requires_dry_run(self): - """The CLI rejects --format=json without --dry-run; emitting JSON - in a real run would race the y/N prompt.""" - work_dir = Path(tempfile.mkdtemp()) - try: - manifest = work_dir / "claude-bottle.json" - manifest.write_text(json.dumps({ - "bottles": {"dev": {}}, - "agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}}, - })) - env = os.environ.copy() - env["HOME"] = str(work_dir) - env.pop("CLAUDE_BOTTLE_DRY_RUN", None) - result = subprocess.run( - [ - sys.executable, str(REPO_ROOT / "cli.py"), - "start", "--format", "json", "demo", - ], - cwd=work_dir, - env=env, - capture_output=True, - text=True, - ) - self.assertNotEqual(0, result.returncode) - finally: - import shutil - shutil.rmtree(work_dir, ignore_errors=True) - def _count_claude_bottle_networks(self) -> int: result = subprocess.run( ["docker", "network", "ls", "--format", "{{.Name}}"], diff --git a/tests/unit/test_cli_start_format.py b/tests/unit/test_cli_start_format.py new file mode 100644 index 0000000..e88f8d8 --- /dev/null +++ b/tests/unit/test_cli_start_format.py @@ -0,0 +1,53 @@ +"""Unit: argparse-level CLI checks for `start --format`. + +Lives in tests/unit/ because nothing here touches Docker — the CLI +exits at argument-validation time before any backend code runs. +""" + +import json +import os +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent.parent + + +class TestStartFormatFlag(unittest.TestCase): + def test_json_format_requires_dry_run(self): + """Emitting JSON in a real run would race the y/N prompt; the + CLI must reject the combination with a message that names the + offending flag.""" + work_dir = Path(tempfile.mkdtemp()) + try: + (work_dir / "claude-bottle.json").write_text(json.dumps({ + "bottles": {"dev": {}}, + "agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}}, + })) + env = os.environ.copy() + env["HOME"] = str(work_dir) + env.pop("CLAUDE_BOTTLE_DRY_RUN", None) + result = subprocess.run( + [ + sys.executable, str(REPO_ROOT / "cli.py"), + "start", "--format", "json", "demo", + ], + cwd=work_dir, + env=env, + capture_output=True, + text=True, + ) + self.assertNotEqual(0, result.returncode) + self.assertIn( + "--format=json requires --dry-run", result.stderr, + f"expected the flag-conflict message; got stderr={result.stderr!r}", + ) + finally: + import shutil + shutil.rmtree(work_dir, ignore_errors=True) + + +if __name__ == "__main__": + unittest.main()