deer-flow/tests/skills/test_data_analysis_export_paths.py
yi-coding eab978f04d
fix(skills): publish complete data-analysis cache generations (#7861)
* fix(skills): publish complete data-analysis cache generations

* fix(skills): clean cache builds when manifest preparation fails

* fix(skills): clean cache generations after failed replacement

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-06 21:12:41 +08:00

112 lines
3.6 KiB
Python

"""Exercise output paths with real DuckDB queries and filesystem exports."""
import csv
import json
import os
import runpy
import subprocess
import sys
from contextlib import chdir
from pathlib import Path
import pytest
duckdb = pytest.importorskip("duckdb")
pytest.importorskip("openpyxl")
SCRIPT = (
Path(__file__).resolve().parents[2]
/ "skills/public/data-analysis/scripts/analyze.py"
)
def assert_export(path, extension, empty=False):
if extension == "csv":
with path.open(encoding="utf-8", newline="") as handle:
assert list(csv.reader(handle)) == (
[["answer", "label"]]
if empty
else [["answer", "label"], ["42", "分析"]]
)
elif extension == "json":
assert json.loads(path.read_text(encoding="utf-8")) == (
[] if empty else [{"answer": 42, "label": "分析"}]
)
else:
assert path.read_text(encoding="utf-8") == (
"| answer | label |\n| --- | --- |\n" + ("" if empty else "| 42 | 分析 |\n")
)
@pytest.mark.parametrize("extension", ["csv", "json", "md"])
@pytest.mark.parametrize("path_kind", ["basename", "dot", "nested", "absolute"])
@pytest.mark.parametrize("empty", [False, True])
def test_query_exports_to_requested_path(tmp_path, extension, path_kind, empty):
analysis = runpy.run_path(str(SCRIPT))
name = f"report.{extension}"
output = {
"basename": name,
"dot": f"./{name}",
"nested": f"new directory/reports/{name}",
"absolute": str(tmp_path / "absolute" / name),
}[path_kind]
sql = "SELECT 42 AS answer, '分析' AS label" + (" WHERE false" if empty else "")
with chdir(tmp_path), duckdb.connect() as con:
result = analysis["action_query"](con, sql, {}, output)
assert result == f"Results exported to {output} ({0 if empty else 1} rows)"
assert_export(Path(output), extension, empty)
@pytest.mark.parametrize("extension", ["csv", "json", "md"])
def test_cli_exports_basename_from_real_read_only_cache(tmp_path, extension):
# Build the real CSV cache without requiring the optional spatial extension.
# The subprocess then exercises main's normal cache-hit and export branches.
temp_dir = tmp_path / "runtime"
cache_dir = temp_dir / ".data-analysis-cache"
cache_dir.mkdir(parents=True)
source = tmp_path / "source.csv"
source.write_text("answer,label\n42,分析\n", encoding="utf-8")
analysis = runpy.run_path(str(SCRIPT))
scope = analysis["main"].__globals__
scope["CACHE_DIR"] = str(cache_dir)
def load_csv(con, files):
table_map = {}
for path in files:
analysis["_load_csv"](con, path, table_map)
return table_map
scope["load_files"] = load_csv
with analysis["open_analysis_cache"]([str(source)]):
pass
output = f"report.{extension}"
completed = subprocess.run(
[
sys.executable,
"-B",
str(SCRIPT),
"--files",
str(source),
"--action",
"query",
"--sql",
"SELECT * FROM source",
"--output-file",
output,
],
cwd=tmp_path,
env={
**os.environ,
"TMPDIR": str(temp_dir),
"TEMP": str(temp_dir),
"TMP": str(temp_dir),
},
capture_output=True,
text=True,
encoding="utf-8",
check=False,
)
assert completed.returncode == 0, completed.stderr
assert "Cache hit!" in completed.stderr
assert f"Results exported to {output} (1 rows)" in completed.stdout
assert_export(tmp_path / output, extension)