joy-cli/tests/test_ai_native.py
feibisi 6531333e44 feat(cli): wrap list roots as {items} in agent mode
Agent/JOY_JSON mode returns {ok,count,items,_joy} for array results
(board ls etc). Compat escape: JOY_LIST_ROOT=array keeps bare arrays.
2026-07-10 12:02:32 +08:00

140 lines
4.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""AI 原生契约JSON、exit code、可发现、agent 模式包装。"""
from __future__ import annotations
import json
import os
import subprocess
import sys
from pathlib import Path
import pytest
SRC = Path(__file__).parent.parent / "src"
sys.path.insert(0, str(SRC))
def run_joy(args: list[str], *, env_extra: dict | None = None, identity: str = "tux") -> subprocess.CompletedProcess:
env = os.environ.copy()
env["JOY_IDENTITY"] = identity
env["PYTHONPATH"] = str(SRC)
# 测试默认 agent 模式(与管道一致)
env.setdefault("JOY_JSON", "1")
if env_extra:
env.update(env_extra)
return subprocess.run(
[sys.executable, "-m", "joy"] + args,
capture_output=True,
text=True,
env=env,
timeout=45,
)
def test_help_json_default_in_agent_mode():
r = run_joy(["help"])
assert r.returncode == 0
data = json.loads(r.stdout)
assert data.get("name") == "joy"
assert "categories" in data
assert data.get("ai_native")
def test_help_text_when_forced():
r = run_joy(["help", "--text"], env_extra={"JOY_JSON": "0", "JOY_AGENT": "0"})
# --text 优先;若仍 agent 环境用 --text on help via cli
r = run_joy(["help"], env_extra={"JOY_JSON": "", "JOY_AGENT": ""})
# 无 env 时非 TTY 仍 agent — 用显式 --text flag 路径:
r = run_joy(["--text", "help"])
assert r.returncode == 0
assert "joy" in r.stdout
assert not r.stdout.strip().startswith("{")
def test_usage_exit_code_2():
r = run_joy(["board"])
assert r.returncode == 2
err = json.loads(r.stderr)
assert err["code"] == "E_USAGE"
assert err.get("ok") is False
def test_unknown_exit_code_1():
r = run_joy(["no-such-cmd-xyz"])
assert r.returncode == 1
err = json.loads(r.stderr)
assert err["code"] == "E_UNKNOWN_CMD"
def test_manifest_ai_native_block():
r = run_joy(["tools", "--manifest"])
assert r.returncode == 0
data = json.loads(r.stdout)
assert "ai_native" in data
assert "stdout_json_success" in data["ai_native"]["principles"]
assert "pipeline" in data["ai_native"]["core_abi"]
assert data["_joy"]["schema_version"] == 1
def test_string_wrapped_as_object_in_agent_mode():
"""agent 模式下若命令仍返回 strcli 应包装。"""
from joy.cli import _normalize_result
out = _normalize_result("hello", command_name="demo", as_agent=True)
assert isinstance(out, dict)
assert out["content"] == "hello"
assert out["_joy"]["schema_version"] == 1
def test_work_transcript_structured():
r = run_joy(["work", "devops", "transcript", "2"])
# 可能无 work-transcript 工具,但结构应是 JSON
if r.returncode == 0 and r.stdout.strip():
data = json.loads(r.stdout)
assert "content" in data or "vm" in data
assert data.get("_joy", {}).get("command") == "work" or "vm" in data
def test_version_json_agent():
r = run_joy(["--version"])
assert r.returncode == 0
data = json.loads(r.stdout)
assert data.get("name") == "joy"
assert "version" in data
def test_perm_error_json():
r = run_joy(["tux", "pending"], identity="wenpai")
assert r.returncode == 1
err = json.loads(r.stderr)
assert err["code"] == "E_PERM"
def test_list_wrapped_in_agent_mode():
from joy.cli import _normalize_result
out = _normalize_result([{"id": 1}, {"id": 2}], command_name="board", as_agent=True)
assert isinstance(out, dict)
assert out["format"] == "list"
assert out["count"] == 2
assert out["items"][0]["id"] == 1
assert out["_joy"].get("list_wrapped") is True
def test_list_raw_compat_env():
"""JOY_LIST_ROOT=array 保持根数组(旧脚本兼容)。"""
r = run_joy(["board", "ls"], env_extra={"JOY_JSON": "1", "JOY_LIST_ROOT": "array"})
if r.returncode == 0 and r.stdout.strip():
data = json.loads(r.stdout)
assert isinstance(data, list), f"expected raw list, got {type(data)}"
def test_board_ls_agent_object():
r = run_joy(["board", "ls"])
if r.returncode != 0:
pytest.skip("board unavailable")
data = json.loads(r.stdout)
assert isinstance(data, dict)
assert "items" in data
assert data.get("format") == "list"
assert data.get("_joy", {}).get("schema_version") == 1