diff --git a/lib/devbase/tui/actions_env.py b/lib/devbase/tui/actions_env.py index 2208cc8..d4eeba7 100644 --- a/lib/devbase/tui/actions_env.py +++ b/lib/devbase/tui/actions_env.py @@ -211,15 +211,8 @@ def _select_scoped_project(devbase_root: Path, message: str, choices): # 各操作の引数収集 + dispatch (plan 2.3 契約) # --------------------------------------------------------------------------- -def _op_init(devbase_root: Path): - # 既存設定がある場合は --reset でやり直し (既存はバックアップされる)。 - reset = flow.need(menu.confirm( - "既存の設定をバックアップしてやり直しますか (--reset)?", default=False)) - return _dispatch(devbase_root, "init", reset=reset) - - def _op_list(devbase_root: Path): - """``env list``: 表示範囲 + 表示オプションを収集して一覧表示する。 + """``env list``: 表示範囲を収集して一覧表示する。 ハンドラ (``cmd_env_list``) は CWD (PWD) が projects/ 配下のときだけ プロジェクト .env を表示するため、プロジェクトを含む表示範囲 @@ -233,13 +226,12 @@ def _op_list(devbase_root: Path): [("グローバル + プロジェクト", "both"), ("グローバルのみ (--global)", "global"), ("プロジェクトのみ (--project)", "project")]) - reveal = flow.need(menu.confirm( - "機密値を伏せ字にせず表示しますか (--reveal)?", default=False)) - keys_only = flow.need(menu.confirm("キー名のみ表示しますか (--keys)?", default=False)) + # --reveal / --keys は CLI 既定 (False = 機密値は伏せ字・通常表示) で実行する + # (非破壊操作の確認プロンプト廃止)。必要な場合は CLI を使う想定。 attrs = {"global_only": scope == "global", "project_only": scope == "project", - "reveal": reveal, "keys_only": keys_only} + "reveal": False, "keys_only": False} if name is None: return _dispatch(devbase_root, "list", **attrs) return _run_in_project(devbase_root, name, @@ -321,9 +313,11 @@ def _op_import(devbase_root: Path): # sync は引数なしで即実行 (ソースファイルから認証情報を再同期する)。 # edit も引数なし。$DEVBASE_ROOT/.env を $EDITOR で開くグローバル操作のため # chdir しない (plan 3.3 は CWD スコープとするが実装を正とする)。 + # init は --reset なし (CLI 既定) で即実行。セットアップ済みなら + # cmd_env_init が案内を出して安全に終了し、やり直しは CLI --reset を使う。 "sync": lambda root: _dispatch(root, "sync"), "edit": lambda root: _dispatch(root, "edit"), - "init": _op_init, + "init": lambda root: _dispatch(root, "init", reset=False), "list": _op_list, "set": _op_set, "get": _op_get, diff --git a/lib/devbase/tui/actions_plugin.py b/lib/devbase/tui/actions_plugin.py index 01e4edf..6cc6815 100644 --- a/lib/devbase/tui/actions_plugin.py +++ b/lib/devbase/tui/actions_plugin.py @@ -27,9 +27,11 @@ # plugin サブコマンド (表示順 = ハイライト既定順)。閲覧系の list を先頭に置き、 # Enter 連打で安全な一覧表示へ到達できるようにする。value は cmd_plugin の subcommand 名 -# (repo のみサブ階層メニューへの分岐)。 +# (repo のみサブ階層メニューへの分岐)。--available は y/N で聞かず独立した +# メニュー項目にする (非破壊操作の確認プロンプト廃止)。 _PLUGIN_OPS: list[tuple[str, str]] = [ - ("一覧表示 (list)", "list"), + ("導入済み一覧 (list)", "list"), + ("利用可能一覧 (list --available)", "list-available"), ("インストール (install)", "install"), ("アンインストール (uninstall)", "uninstall"), ("更新 (update)", "update"), @@ -149,22 +151,13 @@ def _select_repo_operation(): # 各操作の引数収集 + dispatch (plan 2.3 契約) # --------------------------------------------------------------------------- -def _op_list(devbase_root: Path): - # --available: 導入済み一覧の代わりに未導入の利用可能 plugin を表示する。 - available = flow.need(menu.confirm( - "未導入の利用可能 plugin を表示しますか (--available)?", default=False)) - return _dispatch(devbase_root, "list", available=available) - - def _op_install(devbase_root: Path): + # --link / --all は CLI 既定 (False) で実行する。指定が必要な場合は CLI + # (`plugin install --link/--all`) を使う想定 (非破壊操作の確認プロンプト廃止)。 source = flow.need(menu.text( "インストールする plugin の source (名前 / URL / パス)", allow_empty=False)) - link = flow.need(menu.confirm( - "symlink としてインストールしますか (--link)?", default=False)) - install_all = flow.need(menu.confirm( - "リポジトリ内の全 plugin をインストールしますか (--all)?", default=False)) return _dispatch(devbase_root, "install", - source=source, link=link, install_all=install_all) + source=source, link=False, install_all=False) def _op_uninstall(devbase_root: Path): @@ -188,7 +181,9 @@ def _op_info(devbase_root: Path): _OP_HANDLERS = { - "list": _op_list, + # list 系は引数収集なしで即実行 (--available はメニュー項目で分岐)。 + "list": lambda root: _dispatch(root, "list", available=False), + "list-available": lambda root: _dispatch(root, "list", available=True), "install": _op_install, "uninstall": _op_uninstall, "update": _op_update, diff --git a/lib/devbase/tui/actions_project.py b/lib/devbase/tui/actions_project.py index 60ede96..8d5d736 100644 --- a/lib/devbase/tui/actions_project.py +++ b/lib/devbase/tui/actions_project.py @@ -8,8 +8,8 @@ PR2 で running 操作サブメニューを **up/down/login/ps/logs/scale/build/rebuild の全操作** へ拡張した。login/ps/logs/scale は running 中コンテナを対象とするため running 行限定、 stopped/unknown は従来どおり直接 up (PR1 非回帰)。引数を要する操作は ``tui.menu`` の -収集ヘルパで CLI と同じ属性値を集め、破壊的な down は実行前に確認する -(plan 2.3 契約表 / 3.4 破壊的操作確認)。 +収集ヘルパで CLI と同じ属性値を集める (plan 2.3 契約表)。down はデータを失わない +(volume 保持) ためメニュー選択を意思表示とみなし、確認プロンプトは出さない。 プロジェクト一覧の表示・選択は ``tui.app`` (トップ画面) が担い、本モジュールは 選択された 1 行の処理 (``handle_row``) と questionary 不在時のフォールバックを提供する。 @@ -93,11 +93,6 @@ def _select_build_image(devbase_root: Path): # 各操作の引数収集 + dispatch (引数を要する操作のみ。up/rebuild は即実行) # --------------------------------------------------------------------------- -def _op_down(devbase_root: Path, name: str): - flow.confirm_or_back(f"'{name}' のコンテナを停止しますか?") - return dispatch_lifecycle("down", name) - - def _op_login(devbase_root: Path, name: str): # menu.text は空入力 (既定値を消して確定) で "" を返し、wrapper で --index= # と展開されてコマンドが失敗する。menu.integer なら空入力は default=1 を返し、 @@ -106,16 +101,11 @@ def _op_login(devbase_root: Path, name: str): return dispatch_lifecycle("login", name, index=str(index)) -def _op_ps(devbase_root: Path, name: str): - all_c = flow.need(menu.confirm( - "停止中も含め全コンテナを表示しますか (--all)?", default=False)) - return dispatch_lifecycle("ps", name, all=all_c) - - def _op_logs(devbase_root: Path, name: str): - follow = flow.need(menu.confirm("ログを追従表示しますか (--follow)?", default=False)) + # --follow は CLI 既定 (False) で実行する (非破壊操作の確認プロンプト廃止)。 + # 追従が必要な場合は CLI (`project logs --follow`) を使う想定。 tail = flow.need_optional(_optional_int("末尾何行を表示しますか (空で全件)")) - return dispatch_lifecycle("logs", name, follow=follow, tail=tail) + return dispatch_lifecycle("logs", name, follow=False, tail=tail) def _op_scale(devbase_root: Path, name: str): @@ -129,13 +119,15 @@ def _op_build(devbase_root: Path, name: str): _OP_HANDLERS = { - # up/rebuild は引数なしで即実行。up は scale 属性を参照する (常に None。 - # 他コマンドは無視する)。 + # up/down/rebuild/ps は引数なしで即実行。up/rebuild は scale 属性を参照する + # (常に None。他コマンドは無視する)。down はデータを失わない (volume 保持・ + # up で復旧可能) ためメニュー選択を意思表示とみなし、確認プロンプトを出さない。 + # ps の --all は CLI 既定 (False) に揃える。 "up": lambda root, name: dispatch_lifecycle("up", name, scale=None), "rebuild": lambda root, name: dispatch_lifecycle("rebuild", name, scale=None), - "down": _op_down, + "down": lambda root, name: dispatch_lifecycle("down", name), "login": _op_login, - "ps": _op_ps, + "ps": lambda root, name: dispatch_lifecycle("ps", name, all=False), "logs": _op_logs, "scale": _op_scale, "build": _op_build, diff --git a/lib/devbase/tui/actions_snapshot.py b/lib/devbase/tui/actions_snapshot.py index f76b730..2a099de 100644 --- a/lib/devbase/tui/actions_snapshot.py +++ b/lib/devbase/tui/actions_snapshot.py @@ -106,13 +106,13 @@ def _optional_point(message: str): # --------------------------------------------------------------------------- def _op_create(devbase_root: Path): + # --full は CLI 既定 (False = 増分) で実行する (非破壊操作の確認プロンプト + # 廃止)。フルバックアップ強制は CLI (`snapshot create --full`) を使う想定。 name = flow.need(menu.text("スナップショット名 (空でタイムスタンプ自動命名)", allow_empty=True)) - full = flow.need(menu.confirm("フルバックアップを強制しますか (--full)?", - default=False)) # 空入力は CLI の --name 省略と同じ None (自動命名) に正規化する。 return dispatch_group(cmd_snapshot, devbase_root, "create", - name=name or None, full=full) + name=name or None, full=False) def _op_restore(devbase_root: Path): diff --git a/tests/cli/tui/test_actions_env.py b/tests/cli/tui/test_actions_env.py index 9f2a300..83f328d 100644 --- a/tests/cli/tui/test_actions_env.py +++ b/tests/cli/tui/test_actions_env.py @@ -166,30 +166,21 @@ def test_run_operation_edit_is_global_no_project_select(monkeypatch, tmp_path): assert captured["cwd"] == before, "edit は chdir しない" -@pytest.mark.parametrize("reset", [True, False]) -def test_run_operation_init_collects_reset(monkeypatch, tmp_path, reset): - """init は confirm の結果を --reset として渡す (plan 2.3: reset 既定 False)。""" +def test_run_operation_init_runs_without_confirm(monkeypatch, tmp_path): + """init は確認プロンプトなしで reset=False (CLI 既定) のまま即実行する。 + + セットアップ済みの環境では cmd_env_init が案内を出して安全に終了する。 + やり直しは CLI (`env init --reset`) を使う想定。 + """ captured = _capture_dispatch(monkeypatch) - monkeypatch.setattr(menu, "confirm", lambda *a, **k: reset) + monkeypatch.setattr(menu, "confirm", + lambda *a, **k: pytest.fail("init で確認を求めない")) assert actions_env._run_operation(tmp_path, "init") == 0 - assert captured["attrs"] == {"subcommand": "init", "reset": reset} - - -@pytest.mark.parametrize("confirm_ret", ["BACK", None]) -def test_run_operation_init_cancel(monkeypatch, tmp_path, confirm_ret): - """init の confirm で Esc は再表示 (_ARG_CANCEL)、Ctrl-C は全体中止 (None)。""" - from devbase.commands import env as env_mod - called = [] - monkeypatch.setattr(env_mod, "cmd_env", lambda root, args: called.append(1) or 0) - ret = menu.MENU_BACK if confirm_ret == "BACK" else None - monkeypatch.setattr(menu, "confirm", lambda *a, **k: ret) - expected = actions_env._ARG_CANCEL if confirm_ret == "BACK" else None - assert actions_env._run_operation(tmp_path, "init") is expected - assert called == [] + assert captured["attrs"] == {"subcommand": "init", "reset": False} # --------------------------------------------------------------------------- -# _run_operation: list (表示範囲 + reveal/keys) +# _run_operation: list (表示範囲のみ収集。reveal/keys は CLI 既定の False) # --------------------------------------------------------------------------- def test_run_operation_list_global_scope_no_chdir(monkeypatch, tmp_path): @@ -198,15 +189,15 @@ def test_run_operation_list_global_scope_no_chdir(monkeypatch, tmp_path): monkeypatch.setattr(menu, "select", lambda *a, **k: "global") monkeypatch.setattr(actions_env, "_select_project", lambda root: pytest.fail("global でプロジェクト選択してはいけない")) - confirms = iter([True, False]) # reveal=True, keys_only=False - monkeypatch.setattr(menu, "confirm", lambda *a, **k: next(confirms)) + monkeypatch.setattr(menu, "confirm", + lambda *a, **k: pytest.fail("list で確認を求めない")) before = os.getcwd() assert actions_env._run_operation(tmp_path, "list") == 0 assert captured["attrs"] == { "subcommand": "list", "global_only": True, "project_only": False, - "reveal": True, "keys_only": False, + "reveal": False, "keys_only": False, } assert captured["cwd"] == before, "global スコープは chdir しない" @@ -223,8 +214,6 @@ def test_run_operation_list_both_scope_chdirs_and_restores(monkeypatch, tmp_path target.mkdir(parents=True) monkeypatch.setattr(menu, "select", lambda *a, **k: "both") monkeypatch.setattr(actions_env, "_select_project", lambda root: "carmo") - confirms = iter([True, False]) # reveal=True, keys_only=False - monkeypatch.setattr(menu, "confirm", lambda *a, **k: next(confirms)) monkeypatch.setenv("PWD", str(tmp_path)) before = os.getcwd() @@ -232,7 +221,7 @@ def test_run_operation_list_both_scope_chdirs_and_restores(monkeypatch, tmp_path assert captured["attrs"] == { "subcommand": "list", "global_only": False, "project_only": False, - "reveal": True, "keys_only": False, + "reveal": False, "keys_only": False, } # ハンドラ実行中は projects/carmo に居る (グローバル + プロジェクト両方が出る) assert captured["cwd"] == str(target) @@ -253,8 +242,6 @@ def test_run_operation_list_project_chdirs_and_restores(monkeypatch, tmp_path): target.mkdir(parents=True) monkeypatch.setattr(menu, "select", lambda *a, **k: "project") monkeypatch.setattr(actions_env, "_select_project", lambda root: "carmo") - confirms = iter([False, True]) # reveal=False, keys_only=True - monkeypatch.setattr(menu, "confirm", lambda *a, **k: next(confirms)) monkeypatch.setenv("PWD", str(tmp_path)) before = os.getcwd() @@ -262,7 +249,7 @@ def test_run_operation_list_project_chdirs_and_restores(monkeypatch, tmp_path): assert captured["attrs"] == { "subcommand": "list", "global_only": False, "project_only": True, - "reveal": False, "keys_only": True, + "reveal": False, "keys_only": False, } # ハンドラ実行中は projects/carmo に居る (CWD と PWD の両方を切り替える) assert captured["cwd"] == str(target) @@ -273,15 +260,13 @@ def test_run_operation_list_project_chdirs_and_restores(monkeypatch, tmp_path): def test_run_operation_list_project_select_cancel(monkeypatch, tmp_path): - """list のプロジェクト選択を中止したら表示オプション収集にも進まない。""" + """list のプロジェクト選択を中止したら実行しない。""" from devbase.commands import env as env_mod called = [] monkeypatch.setattr(env_mod, "cmd_env", lambda root, args: called.append(1) or 0) monkeypatch.setattr(menu, "select", lambda *a, **k: "project") monkeypatch.setattr(actions_env, "_select_project", lambda root: actions_env._ARG_CANCEL) - monkeypatch.setattr(menu, "confirm", - lambda *a, **k: pytest.fail("選択中止後に確認を求めない")) assert actions_env._run_operation(tmp_path, "list") is actions_env._ARG_CANCEL assert called == [] @@ -299,20 +284,6 @@ def test_run_operation_list_scope_cancel(monkeypatch, tmp_path, scope_ret): assert called == [] -@pytest.mark.parametrize("confirm_ret", ["BACK", None]) -def test_run_operation_list_confirm_cancel(monkeypatch, tmp_path, confirm_ret): - """reveal の confirm で Esc は再表示 (_ARG_CANCEL)、Ctrl-C は全体中止 (None)。""" - from devbase.commands import env as env_mod - called = [] - monkeypatch.setattr(env_mod, "cmd_env", lambda root, args: called.append(1) or 0) - monkeypatch.setattr(menu, "select", lambda *a, **k: "global") - ret = menu.MENU_BACK if confirm_ret == "BACK" else None - monkeypatch.setattr(menu, "confirm", lambda *a, **k: ret) - expected = actions_env._ARG_CANCEL if confirm_ret == "BACK" else None - assert actions_env._run_operation(tmp_path, "list") is expected - assert called == [] - - # --------------------------------------------------------------------------- # _run_operation: get / delete # --------------------------------------------------------------------------- diff --git a/tests/cli/tui/test_actions_plugin.py b/tests/cli/tui/test_actions_plugin.py index 93aa30a..dbcc586 100644 --- a/tests/cli/tui/test_actions_plugin.py +++ b/tests/cli/tui/test_actions_plugin.py @@ -159,9 +159,11 @@ def fake_select(message, choices, *, back, search): assert actions_plugin._select_operation() == "list" assert captured["back"] is True assert captured["search"] is False - # 閲覧系の list を先頭にしつつ全 8 操作 (repo 含む) を提示する。 + # 閲覧系の list 2 種を先頭にしつつ全 9 操作 (repo 含む) を提示する。 + # --available は y/N で聞かず独立したメニュー項目で提供する。 assert captured["values"] == [ - "list", "install", "uninstall", "update", "info", "sync", "migrate", "repo"] + "list", "list-available", "install", "uninstall", "update", "info", + "sync", "migrate", "repo"] def test_select_repo_operation_lists_all_ops(monkeypatch): @@ -181,35 +183,27 @@ def fake_select(message, choices, *, back, search): # _run_operation: 各操作の引数収集 + dispatch 契約 (plan 2.3) # --------------------------------------------------------------------------- -@pytest.mark.parametrize("available", [True, False]) -def test_run_operation_list_available_flag(monkeypatch, tmp_path, available): +@pytest.mark.parametrize("op,available", [("list", False), ("list-available", True)]) +def test_run_operation_list_available_flag(monkeypatch, tmp_path, op, available): + """list 系は確認プロンプトなしで即実行する (--available はメニュー項目で分岐)。""" captured = _capture_dispatch(monkeypatch) - monkeypatch.setattr(menu, "confirm", lambda *a, **k: available) - assert actions_plugin._run_operation(tmp_path, "list") == 0 + monkeypatch.setattr(menu, "confirm", + lambda *a, **k: pytest.fail("list で確認を求めない")) + assert actions_plugin._run_operation(tmp_path, op) == 0 assert captured["subcommand"] == "list" assert captured["available"] is available -@pytest.mark.parametrize("confirm_ret", ["BACK", None]) -def test_run_operation_list_cancel(monkeypatch, tmp_path, confirm_ret): - """confirm で Esc は再表示 (_ARG_CANCEL)、Ctrl-C は全体中止 (None)。""" - called = _no_dispatch(monkeypatch) - ret = menu.MENU_BACK if confirm_ret == "BACK" else None - monkeypatch.setattr(menu, "confirm", lambda *a, **k: ret) - expected = actions_plugin._ARG_CANCEL if confirm_ret == "BACK" else None - assert actions_plugin._run_operation(tmp_path, "list") is expected - assert called == [] - - -def test_run_operation_install_collects_source_link_all(monkeypatch, tmp_path): +def test_run_operation_install_collects_source_only(monkeypatch, tmp_path): + """install は source のみ収集し、--link/--all は CLI 既定 (False) で実行する。""" captured = _capture_dispatch(monkeypatch) monkeypatch.setattr(menu, "text", lambda *a, **k: "owner/repo") - confirms = iter([True, False]) # link=True, install_all=False - monkeypatch.setattr(menu, "confirm", lambda *a, **k: next(confirms)) + monkeypatch.setattr(menu, "confirm", + lambda *a, **k: pytest.fail("install で確認を求めない")) assert actions_plugin._run_operation(tmp_path, "install") == 0 assert captured["subcommand"] == "install" assert captured["source"] == "owner/repo" - assert captured["link"] is True and captured["install_all"] is False + assert captured["link"] is False and captured["install_all"] is False @pytest.mark.parametrize("text_ret", ["BACK", None]) @@ -223,20 +217,6 @@ def test_run_operation_install_source_cancel(monkeypatch, tmp_path, text_ret): assert called == [] -@pytest.mark.parametrize("confirm_ret", ["BACK", None]) -def test_run_operation_install_confirm_cancel_midway(monkeypatch, tmp_path, - confirm_ret): - """install の途中 (install_all) で Esc / Ctrl-C したら実行しない。""" - called = _no_dispatch(monkeypatch) - monkeypatch.setattr(menu, "text", lambda *a, **k: "owner/repo") - ret = menu.MENU_BACK if confirm_ret == "BACK" else None - confirms = iter([True, ret]) # link=True, install_all で中止 - monkeypatch.setattr(menu, "confirm", lambda *a, **k: next(confirms)) - expected = actions_plugin._ARG_CANCEL if confirm_ret == "BACK" else None - assert actions_plugin._run_operation(tmp_path, "install") is expected - assert called == [] - - def test_run_operation_uninstall_confirmed(monkeypatch, tmp_path): """uninstall は一覧から選んだ name で confirm=True のとき実行する (plan 3.4)。""" captured = _capture_dispatch(monkeypatch) diff --git a/tests/cli/tui/test_actions_project.py b/tests/cli/tui/test_actions_project.py index dac56b8..e949a0e 100644 --- a/tests/cli/tui/test_actions_project.py +++ b/tests/cli/tui/test_actions_project.py @@ -147,30 +147,15 @@ def test_run_operation_rebuild(monkeypatch, tmp_path): assert captured["subcommand"] == "rebuild" and captured["name"] == "carmo" -def test_run_operation_down_confirmed(monkeypatch, tmp_path): - """down は confirm=True で停止を実行する (plan 3.4)。""" +def test_run_operation_down_runs_without_confirm(monkeypatch, tmp_path): + """down は確認プロンプトなしで即実行する (volume 保持・up で復旧可能)。""" captured = _capture_dispatch(monkeypatch) - monkeypatch.setattr(menu, "confirm", lambda *a, **k: True) + monkeypatch.setattr(menu, "confirm", + lambda *a, **k: pytest.fail("down で確認を求めない")) assert actions_project._run_operation(tmp_path, "carmo", "down") == 0 assert captured["subcommand"] == "down" -@pytest.mark.parametrize("confirm_ret", [False, "BACK", None]) -def test_run_operation_down_cancelled_does_not_dispatch(monkeypatch, tmp_path, confirm_ret): - """down の confirm を拒否 (False) / Esc / Ctrl-C したら停止しない。 - - 拒否と Esc はサブメニュー再表示 (_ARG_CANCEL)、Ctrl-C は全体中止 (None)。 - """ - from devbase.commands import container as container_mod - called = [] - monkeypatch.setattr(container_mod, "cmd_project", lambda args: called.append(1) or 0) - ret = menu.MENU_BACK if confirm_ret == "BACK" else confirm_ret - monkeypatch.setattr(menu, "confirm", lambda *a, **k: ret) - expected = None if confirm_ret is None else actions_project._ARG_CANCEL - assert actions_project._run_operation(tmp_path, "carmo", "down") is expected - assert called == [], "確認を拒否/中止したら down しない" - - def test_run_operation_login_collects_index(monkeypatch, tmp_path): captured = _capture_dispatch(monkeypatch) # menu.integer で正の整数を保証し、index は文字列契約のため str 化して渡す。 @@ -192,25 +177,28 @@ def test_run_operation_login_cancel(monkeypatch, tmp_path, int_ret): assert called == [] -def test_run_operation_ps_all_flag(monkeypatch, tmp_path): +def test_run_operation_ps_runs_without_confirm(monkeypatch, tmp_path): + """ps は確認プロンプトなしで即実行する (--all は CLI 既定の False)。""" captured = _capture_dispatch(monkeypatch) - monkeypatch.setattr(menu, "confirm", lambda *a, **k: True) + monkeypatch.setattr(menu, "confirm", + lambda *a, **k: pytest.fail("ps で確認を求めない")) assert actions_project._run_operation(tmp_path, "carmo", "ps") == 0 - assert captured["subcommand"] == "ps" and captured["all"] is True + assert captured["subcommand"] == "ps" and captured["all"] is False -def test_run_operation_logs_follow_and_tail(monkeypatch, tmp_path): +def test_run_operation_logs_collects_tail_only(monkeypatch, tmp_path): + """logs は tail のみ収集し、--follow は CLI 既定 (False) で実行する。""" captured = _capture_dispatch(monkeypatch) - monkeypatch.setattr(menu, "confirm", lambda *a, **k: True) # follow + monkeypatch.setattr(menu, "confirm", + lambda *a, **k: pytest.fail("logs で確認を求めない")) monkeypatch.setattr(actions_project, "_optional_int", lambda msg: 50) # tail=50 assert actions_project._run_operation(tmp_path, "carmo", "logs") == 0 assert captured["subcommand"] == "logs" - assert captured["follow"] is True and captured["tail"] == 50 + assert captured["follow"] is False and captured["tail"] == 50 def test_run_operation_logs_tail_empty_is_none(monkeypatch, tmp_path): captured = _capture_dispatch(monkeypatch) - monkeypatch.setattr(menu, "confirm", lambda *a, **k: False) monkeypatch.setattr(actions_project, "_optional_int", lambda msg: None) # 空 = 全件 assert actions_project._run_operation(tmp_path, "carmo", "logs") == 0 assert captured["follow"] is False and captured["tail"] is None @@ -221,7 +209,6 @@ def test_run_operation_logs_tail_ctrl_c_aborts(monkeypatch, tmp_path): from devbase.commands import container as container_mod called = [] monkeypatch.setattr(container_mod, "cmd_project", lambda args: called.append(1) or 0) - monkeypatch.setattr(menu, "confirm", lambda *a, **k: True) monkeypatch.setattr(actions_project, "_optional_int", lambda msg: actions_project._ABORT) assert actions_project._run_operation(tmp_path, "carmo", "logs") is None diff --git a/tests/cli/tui/test_actions_snapshot.py b/tests/cli/tui/test_actions_snapshot.py index 3351c7d..fd00c75 100644 --- a/tests/cli/tui/test_actions_snapshot.py +++ b/tests/cli/tui/test_actions_snapshot.py @@ -121,20 +121,21 @@ def test_run_operation_list_no_extra_attrs(monkeypatch, tmp_path): assert captured == {"devbase_root": tmp_path, "subcommand": "list"} -def test_run_operation_create_collects_name_and_full(monkeypatch, tmp_path): +def test_run_operation_create_collects_name_only(monkeypatch, tmp_path): + """create は name のみ収集し、--full は CLI 既定 (False = 増分) で実行する。""" captured = _capture_dispatch(monkeypatch) monkeypatch.setattr(menu, "text", lambda *a, **k: "snap1") - monkeypatch.setattr(menu, "confirm", lambda *a, **k: True) # --full + monkeypatch.setattr(menu, "confirm", + lambda *a, **k: pytest.fail("create で確認を求めない")) assert actions_snapshot._run_operation(tmp_path, "create") == 0 assert captured["subcommand"] == "create" - assert captured["name"] == "snap1" and captured["full"] is True + assert captured["name"] == "snap1" and captured["full"] is False def test_run_operation_create_empty_name_is_none(monkeypatch, tmp_path): """空入力の name は CLI の --name 省略と同じ None (自動命名) に正規化する。""" captured = _capture_dispatch(monkeypatch) monkeypatch.setattr(menu, "text", lambda *a, **k: "") - monkeypatch.setattr(menu, "confirm", lambda *a, **k: False) assert actions_snapshot._run_operation(tmp_path, "create") == 0 assert captured["name"] is None and captured["full"] is False @@ -150,18 +151,6 @@ def test_run_operation_create_name_cancel(monkeypatch, tmp_path, text_ret): assert called == [] -@pytest.mark.parametrize("confirm_ret", ["BACK", None]) -def test_run_operation_create_full_cancel(monkeypatch, tmp_path, confirm_ret): - """--full の confirm で Esc は再表示 (_ARG_CANCEL)、Ctrl-C は全体中止 (None)。""" - called = _no_dispatch(monkeypatch) - monkeypatch.setattr(menu, "text", lambda *a, **k: "snap1") - ret = menu.MENU_BACK if confirm_ret == "BACK" else None - monkeypatch.setattr(menu, "confirm", lambda *a, **k: ret) - expected = actions_snapshot._ARG_CANCEL if confirm_ret == "BACK" else None - assert actions_snapshot._run_operation(tmp_path, "create") is expected - assert called == [] - - def test_run_operation_restore_confirmed(monkeypatch, tmp_path): """restore は confirm=True で name/point を契約どおり渡す (plan 3.4)。""" captured = _capture_dispatch(monkeypatch)