mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 21:26:39 +00:00
* fix(chart-visualization): wire generate_spreadsheet and fail CLI nonzero SKILL.md already selects generate_spreadsheet and the reference file documents it, but CHART_TYPE_MAP had no entry, so the CLI reported Unknown tool. Map it to the AntV spreadsheet type and exit non-zero after any unknown tool, missing tool, or HTTP failure. Successful chart URLs stay on stdout. Co-authored-by: wannT-1 <200890097+wannT-1@users.noreply.github.com> * docs(agents): fold chart CLI failures into the shared stderr/stdout note The sandbox guidance chain was over the AG002 hard limit. Share one nonzero-exit rule for video, podcast, and chart-visualization CLIs. Co-authored-by: wannT-1 <wannT-1@users.noreply.github.com> --------- Co-authored-by: wannT-1 <200890097+wannT-1@users.noreply.github.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com> Co-authored-by: wannT-1 <wannT-1@users.noreply.github.com>
167 lines
5.0 KiB
Python
167 lines
5.0 KiB
Python
"""Chart-visualization CLI regressions: spreadsheet mapping and nonzero failures.
|
|
|
|
The AntV request is mocked with a Node ``--require`` preload so the test stays
|
|
offline. Successful chart URLs stay on stdout; diagnostics stay on stderr.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
SCRIPT = REPO_ROOT / "skills" / "public" / "chart-visualization" / "scripts" / "generate.js"
|
|
SKILL = REPO_ROOT / "skills" / "public" / "chart-visualization" / "SKILL.md"
|
|
NODE = shutil.which("node")
|
|
|
|
FETCH_PRELOAD = """\
|
|
const fs = require("fs");
|
|
|
|
global.fetch = async (url, options) => {
|
|
const capture = process.env.CHART_VIS_CAPTURE;
|
|
if (capture) {
|
|
fs.appendFileSync(
|
|
capture,
|
|
JSON.stringify({ url: String(url), body: options && options.body }) + "\\n",
|
|
);
|
|
}
|
|
const status = Number(process.env.CHART_VIS_HTTP_STATUS || 200);
|
|
if (status >= 400) {
|
|
return {
|
|
ok: false,
|
|
status,
|
|
text: async () => "upstream failed",
|
|
json: async () => ({}),
|
|
};
|
|
}
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
text: async () => "",
|
|
json: async () => ({
|
|
success: true,
|
|
resultObj: "https://example.test/sheet.png",
|
|
}),
|
|
};
|
|
};
|
|
"""
|
|
|
|
|
|
pytestmark = pytest.mark.skipif(NODE is None, reason="node is required to run generate.js")
|
|
|
|
|
|
def _run(tmp_path, spec, *, http_status=None):
|
|
preload = tmp_path / "mock_fetch.js"
|
|
preload.write_text(FETCH_PRELOAD, encoding="utf-8")
|
|
capture = tmp_path / "capture.jsonl"
|
|
env = os.environ.copy()
|
|
env["CHART_VIS_CAPTURE"] = str(capture)
|
|
env["VIS_REQUEST_SERVER"] = "http://127.0.0.1:9/api/gpt-vis"
|
|
env.pop("SERVICE_ID", None)
|
|
if http_status is None:
|
|
env.pop("CHART_VIS_HTTP_STATUS", None)
|
|
else:
|
|
env["CHART_VIS_HTTP_STATUS"] = str(http_status)
|
|
|
|
result = subprocess.run(
|
|
[NODE, "--require", str(preload), str(SCRIPT), json.dumps(spec)],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
timeout=15,
|
|
env=env,
|
|
)
|
|
calls = []
|
|
if capture.exists():
|
|
calls = [json.loads(line) for line in capture.read_text(encoding="utf-8").splitlines() if line]
|
|
return result, calls
|
|
|
|
|
|
def _chart_type_map():
|
|
probe = subprocess.run(
|
|
[
|
|
NODE,
|
|
"-e",
|
|
"const {CHART_TYPE_MAP}=require(process.argv[1]); process.stdout.write(JSON.stringify(CHART_TYPE_MAP));",
|
|
str(SCRIPT),
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
timeout=15,
|
|
)
|
|
assert probe.returncode == 0, probe.stderr
|
|
return json.loads(probe.stdout)
|
|
|
|
|
|
def test_generate_spreadsheet_is_mapped_and_prints_url(tmp_path):
|
|
chart_types = _chart_type_map()
|
|
skill = SKILL.read_text(encoding="utf-8-sig")
|
|
assert "26 available options" in skill
|
|
assert len(chart_types) == 26
|
|
assert chart_types["generate_spreadsheet"] == "spreadsheet"
|
|
|
|
spec = {
|
|
"tool": "generate_spreadsheet",
|
|
"args": {"data": [{"name": "Jane", "age": 25}], "title": "People"},
|
|
}
|
|
result, calls = _run(tmp_path, spec)
|
|
|
|
assert result.returncode == 0
|
|
assert result.stdout == "https://example.test/sheet.png\n"
|
|
assert result.stderr == ""
|
|
assert "Unknown tool" not in result.stderr
|
|
assert len(calls) == 1
|
|
body = json.loads(calls[0]["body"])
|
|
assert body["type"] == "spreadsheet"
|
|
assert body["source"] == "chart-visualization-creator"
|
|
assert body["data"] == [{"name": "Jane", "age": 25}]
|
|
assert body["title"] == "People"
|
|
assert calls[0]["url"] == "http://127.0.0.1:9/api/gpt-vis"
|
|
|
|
|
|
def test_unknown_tool_exits_nonzero_without_calling_the_service(tmp_path):
|
|
result, calls = _run(tmp_path, {"tool": "generate_not_a_chart", "args": {}})
|
|
|
|
assert result.returncode == 1
|
|
assert result.stdout == ""
|
|
assert "Unknown tool 'generate_not_a_chart'" in result.stderr
|
|
assert calls == []
|
|
|
|
|
|
def test_missing_tool_exits_nonzero(tmp_path):
|
|
result, calls = _run(tmp_path, {"args": {"data": []}})
|
|
|
|
assert result.returncode == 1
|
|
assert result.stdout == ""
|
|
assert "tool" in result.stderr
|
|
assert calls == []
|
|
|
|
|
|
def test_http_failure_exits_nonzero(tmp_path):
|
|
spec = {"tool": "generate_spreadsheet", "args": {"data": [{"name": "Jane"}]}}
|
|
result, calls = _run(tmp_path, spec, http_status=502)
|
|
|
|
assert result.returncode == 1
|
|
assert result.stdout == ""
|
|
assert "HTTP 502" in result.stderr
|
|
assert len(calls) == 1
|
|
assert json.loads(calls[0]["body"])["type"] == "spreadsheet"
|
|
|
|
|
|
def test_batch_keeps_successful_urls_and_still_exits_nonzero(tmp_path):
|
|
spec = [
|
|
{"tool": "generate_not_a_chart"},
|
|
{"tool": "generate_spreadsheet", "args": {"data": [{"name": "Ada"}]}},
|
|
]
|
|
result, calls = _run(tmp_path, spec)
|
|
|
|
assert result.returncode == 1
|
|
assert result.stdout == "https://example.test/sheet.png\n"
|
|
assert "Unknown tool 'generate_not_a_chart'" in result.stderr
|
|
assert len(calls) == 1
|
|
assert json.loads(calls[0]["body"])["type"] == "spreadsheet"
|