chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# Dify Workflow Harness Test Documentation
|
||||
|
||||
## Test Inventory Plan
|
||||
|
||||
- `test_core.py`: backend discovery, command construction, packaging fixture checks
|
||||
- `test_full_e2e.py`: wrapper subprocess smoke tests and workflow lifecycle forwarding
|
||||
|
||||
## Unit Test Plan
|
||||
|
||||
### utils/dify_workflow_backend.py
|
||||
|
||||
- resolve the upstream `dify-workflow` executable from PATH
|
||||
- fall back to `python -m dify_workflow.cli` when only the Python package exists
|
||||
- raise clear install guidance when neither exists
|
||||
- build the final subprocess command correctly
|
||||
|
||||
### Packaging fixtures
|
||||
|
||||
- README documents two-step installation
|
||||
- SKILL.md documents wrapper semantics and installation
|
||||
- wrapper CLI exposes the expected top-level commands
|
||||
|
||||
## E2E Test Plan
|
||||
|
||||
- verify `--help` on the wrapper command
|
||||
- create a minimal workflow through the wrapper when the upstream CLI is installed
|
||||
- validate the created workflow through the wrapper
|
||||
- inspect the workflow as JSON through the wrapper
|
||||
|
||||
## Notes
|
||||
|
||||
This harness wraps an external open-source CLI rather than shipping the workflow
|
||||
engine itself. Full E2E tests therefore require the upstream Dify workflow CLI
|
||||
to be installed locally before the suite is run. If the upstream CLI is missing
|
||||
or broken, the E2E suite now fails explicitly instead of being skipped.
|
||||
|
||||
## Test Results
|
||||
|
||||
Verified on 2026-04-07 in the local CLI-Anything worktree.
|
||||
|
||||
### Commands Run
|
||||
|
||||
```bash
|
||||
uv run --with pytest --with click --with prompt-toolkit --with-editable "c:\Users\lishun\py_ws\提交pr\dify-workflow-cli" python -m pytest cli_anything/dify_workflow/tests -vv -s
|
||||
```
|
||||
|
||||
### Results
|
||||
|
||||
- total collected: 11
|
||||
- passed: 11
|
||||
|
||||
### Notes
|
||||
|
||||
- Full forwarding tests were rerun with the upstream `dify-workflow` package installed editable from the local `dify-workflow-cli` repository.
|
||||
- The wrapper suite now exercises `help`, `create`, `validate`, and `inspect -j` end-to-end against the real upstream CLI.
|
||||
@@ -0,0 +1 @@
|
||||
"""Tests for the Dify Workflow harness."""
|
||||
@@ -0,0 +1,67 @@
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
from cli_anything.dify_workflow.dify_workflow_cli import cli
|
||||
from cli_anything.dify_workflow.utils.dify_workflow_backend import (
|
||||
build_command,
|
||||
has_upstream_cli,
|
||||
require_dify_workflow_command,
|
||||
)
|
||||
|
||||
|
||||
class TestBackendDiscovery:
|
||||
def test_require_command_returns_binary_path(self):
|
||||
with patch("cli_anything.dify_workflow.utils.dify_workflow_backend.shutil.which", return_value="/usr/bin/dify-workflow"):
|
||||
assert require_dify_workflow_command() == ["/usr/bin/dify-workflow"]
|
||||
|
||||
def test_require_command_falls_back_to_python_module(self):
|
||||
with patch("cli_anything.dify_workflow.utils.dify_workflow_backend.shutil.which", return_value=None), patch(
|
||||
"cli_anything.dify_workflow.utils.dify_workflow_backend.importlib.util.find_spec",
|
||||
return_value=object(),
|
||||
), patch("cli_anything.dify_workflow.utils.dify_workflow_backend.sys.executable", "python"):
|
||||
assert require_dify_workflow_command() == ["python", "-m", "dify_workflow.cli"]
|
||||
|
||||
def test_require_command_raises_install_guidance(self):
|
||||
with patch("cli_anything.dify_workflow.utils.dify_workflow_backend.shutil.which", return_value=None), patch(
|
||||
"cli_anything.dify_workflow.utils.dify_workflow_backend.importlib.util.find_spec",
|
||||
return_value=None,
|
||||
):
|
||||
with pytest.raises(RuntimeError, match="dify-workflow command not found"):
|
||||
require_dify_workflow_command()
|
||||
|
||||
def test_build_command_appends_args(self):
|
||||
with patch("cli_anything.dify_workflow.utils.dify_workflow_backend.require_dify_workflow_command", return_value=["dify-workflow"]):
|
||||
assert build_command(["guide", "-j"]) == ["dify-workflow", "guide", "-j"]
|
||||
|
||||
def test_has_upstream_cli_false_when_resolution_fails(self):
|
||||
with patch("cli_anything.dify_workflow.utils.dify_workflow_backend.require_dify_workflow_command", side_effect=RuntimeError("missing")):
|
||||
assert has_upstream_cli() is False
|
||||
|
||||
|
||||
class TestWrapperCLI:
|
||||
def test_help_renders(self):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "CLI-Anything wrapper for the Dify workflow DSL editor" in result.output
|
||||
assert "edit" in result.output
|
||||
assert "config" in result.output
|
||||
|
||||
|
||||
class TestPackagingFixtures:
|
||||
def test_readme_documents_two_step_install(self):
|
||||
package_root = Path(__file__).resolve().parents[1]
|
||||
readme = (package_root / "README.md").read_text(encoding="utf-8")
|
||||
assert "Install the upstream Dify workflow CLI first" in readme
|
||||
assert "git+https://github.com/Akabane71/dify-workflow-cli.git@main" in readme
|
||||
|
||||
def test_skill_file_documents_wrapper_behavior(self):
|
||||
package_root = Path(__file__).resolve().parents[1]
|
||||
skill = (package_root / "skills" / "SKILL.md").read_text(encoding="utf-8")
|
||||
assert "wrapper" in skill.lower()
|
||||
assert "cli-anything-dify-workflow" in skill
|
||||
assert "dify-workflow" in skill
|
||||
@@ -0,0 +1,84 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from cli_anything.dify_workflow.utils.dify_workflow_backend import require_dify_workflow_command
|
||||
|
||||
|
||||
def _decode_output(data: bytes | None) -> str:
|
||||
if not data:
|
||||
return ""
|
||||
return data.decode("utf-8", errors="replace")
|
||||
|
||||
|
||||
def _require_working_upstream() -> None:
|
||||
command = require_dify_workflow_command()
|
||||
result = subprocess.run(
|
||||
command + ["--help"],
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
stderr = _decode_output(result.stderr)
|
||||
stdout = _decode_output(result.stdout)
|
||||
raise RuntimeError(
|
||||
"upstream dify-workflow CLI is required for wrapper E2E tests and must respond to --help.\n"
|
||||
f"stdout: {stdout}\n"
|
||||
f"stderr: {stderr}"
|
||||
)
|
||||
|
||||
|
||||
def _resolve_cli(name: str, module: str) -> list[str]:
|
||||
force = os.environ.get("CLI_ANYTHING_FORCE_INSTALLED", "").strip() == "1"
|
||||
path = shutil.which(name)
|
||||
if path:
|
||||
return [path]
|
||||
if force:
|
||||
raise RuntimeError(f"{name} not found in PATH. Install with: pip install -e .")
|
||||
return [sys.executable, "-m", module]
|
||||
|
||||
|
||||
class TestWrapperE2E:
|
||||
CLI_BASE = _resolve_cli("cli-anything-dify-workflow", "cli_anything.dify_workflow")
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
_require_working_upstream()
|
||||
|
||||
def _run(self, args, check=True):
|
||||
result = subprocess.run(self.CLI_BASE + args, capture_output=True, check=False)
|
||||
stdout = _decode_output(result.stdout)
|
||||
stderr = _decode_output(result.stderr)
|
||||
if check and result.returncode != 0:
|
||||
raise subprocess.CalledProcessError(result.returncode, result.args, output=stdout, stderr=stderr)
|
||||
result.stdout = stdout
|
||||
result.stderr = stderr
|
||||
return result
|
||||
|
||||
def test_help(self):
|
||||
result = self._run(["--help"])
|
||||
assert result.returncode == 0
|
||||
assert "create" in result.stdout
|
||||
|
||||
def test_create_and_validate_workflow(self, tmp_path):
|
||||
workflow_path = tmp_path / "workflow.yaml"
|
||||
|
||||
result = self._run(["create", "-o", str(workflow_path), "--template", "llm", "-j"])
|
||||
assert result.returncode == 0, result.stderr
|
||||
data = json.loads(result.stdout)
|
||||
assert data["status"] == "created"
|
||||
|
||||
result = self._run(["validate", str(workflow_path), "-j"])
|
||||
assert result.returncode == 0, result.stderr
|
||||
report = json.loads(result.stdout)
|
||||
assert report["valid"] is True
|
||||
|
||||
def test_inspect_json(self, tmp_path):
|
||||
workflow_path = tmp_path / "workflow.yaml"
|
||||
self._run(["create", "-o", str(workflow_path)], check=True)
|
||||
|
||||
result = self._run(["inspect", str(workflow_path), "-j"])
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert result.stdout.strip().startswith("{")
|
||||
Reference in New Issue
Block a user