From 10b19d84578521d206e8f51023385fcf91fffa84 Mon Sep 17 00:00:00 2001 From: JeffreyChen Date: Tue, 28 Apr 2026 14:25:32 +0800 Subject: [PATCH] Restore Windows double-decode for --execute_str PyBreeze double-encodes the action list on Windows to survive command-line escaping; the registry refactor dropped the matching peel step, so the CLI saw a string and rejected the batch. Restore the unwrap with an isinstance(parsed, str) guard so single- and double-encoded payloads both work cross-platform. Cover both shapes in tests/test_cli_main.py. --- automation_file/__main__.py | 9 ++++++++- tests/test_cli_main.py | 40 +++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/automation_file/__main__.py b/automation_file/__main__.py index 51d6c8f..5e48c92 100644 --- a/automation_file/__main__.py +++ b/automation_file/__main__.py @@ -38,7 +38,14 @@ def _execute_dir(path: str) -> Any: def _execute_str(raw: str) -> Any: - return execute_action(json.loads(raw)) + parsed = json.loads(raw) + # PyBreeze (and other launchers) double-encode the action list on Windows + # to survive command-line escaping — peel the second JSON layer if we got + # a string back. Guarded by isinstance, so callers that pass a single-encoded + # payload still work on every platform. + if isinstance(parsed, str): + parsed = json.loads(parsed) + return execute_action(parsed) _LEGACY_DISPATCH: dict[str, Callable[[str], Any]] = { diff --git a/tests/test_cli_main.py b/tests/test_cli_main.py index a3c4bbb..a0c9389 100644 --- a/tests/test_cli_main.py +++ b/tests/test_cli_main.py @@ -2,6 +2,9 @@ from __future__ import annotations +import json +from typing import Any + import pytest from automation_file import __main__ as cli_main @@ -55,3 +58,40 @@ def _fake_cli(argv: list[str] | None = None) -> int: assert rc == 0 assert "--allowed-actions" not in captured["argv"] assert captured["argv"] == ["--name", "automation_file", "--version", "1.0.0"] + + +def _capture_execute_action(monkeypatch: pytest.MonkeyPatch) -> list[Any]: + received: list[Any] = [] + + def _fake_execute(action_list: Any) -> dict: + received.append(action_list) + return {} + + monkeypatch.setattr(cli_main, "execute_action", _fake_execute) + return received + + +def test_execute_str_accepts_single_encoded_payload( + monkeypatch: pytest.MonkeyPatch, +) -> None: + received = _capture_execute_action(monkeypatch) + actions = [["FA_create_file", {"file_path": "x.txt", "content": "hi"}]] + + rc = cli_main.main(["--execute_str", json.dumps(actions)]) + + assert rc == 0 + assert received == [actions] + + +def test_execute_str_accepts_double_encoded_payload( + monkeypatch: pytest.MonkeyPatch, +) -> None: + received = _capture_execute_action(monkeypatch) + actions = [["FA_create_file", {"file_path": "x.txt", "content": "hi"}]] + + # PyBreeze on Windows wraps the JSON list once more before handing the + # argument to subprocess; the CLI must peel both layers off. + rc = cli_main.main(["--execute_str", json.dumps(json.dumps(actions))]) + + assert rc == 0 + assert received == [actions]