Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

165 lines
6.4 KiB
Python

"""Tests for the Pi coding tool: metadata, gating, validators, lifecycle, error_kind."""
from __future__ import annotations
import os
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from integrations.pi import PiCodingResult
from tools.pi_coding_tool import PiCodingTool, pi_coding_task
from tools.pi_coding_tool.errors import PiCodingError
from tools.pi_coding_tool.validation import validate_model, validate_task, validate_workspace
_VERIFY = "tools.pi_coding_tool.runner.verify_pi_coding"
_RUN = "tools.pi_coding_tool.runner.run_pi_coding_task"
# --------------------------------------------------------------------------- #
# metadata + availability
# --------------------------------------------------------------------------- #
def test_metadata_is_mutating_on_investigation_surface() -> None:
t = pi_coding_task
assert t.name == "pi_coding_task"
assert t.source == "knowledge"
assert t.side_effect_level == "mutating"
assert t.requires_approval is False
assert t.surfaces == ("investigation",)
assert t.input_schema["required"] == ["task"]
assert "error_kind" in t.outputs
assert t.metadata().name == "pi_coding_task"
def test_is_available_off_by_default_then_opt_in() -> None:
with patch.dict(os.environ, {}, clear=False):
os.environ.pop("PI_CODING_ENABLED", None)
assert pi_coding_task.is_available({}) is False
with patch.dict(os.environ, {"PI_CODING_ENABLED": "1"}, clear=False):
assert pi_coding_task.is_available({}) is True
# --------------------------------------------------------------------------- #
# validators
# --------------------------------------------------------------------------- #
def test_validate_task() -> None:
assert validate_task(" do it ") == "do it"
with pytest.raises(PiCodingError) as empty:
validate_task(" ")
assert empty.value.kind == "invalid_input"
with pytest.raises(PiCodingError) as too_long:
validate_task("x" * 5000)
assert too_long.value.kind == "invalid_input"
def test_validate_workspace(tmp_path: Path) -> None:
assert validate_workspace(str(tmp_path)) == str(tmp_path)
with pytest.raises(PiCodingError) as missing:
validate_workspace("/no/such/path/xyz123")
assert missing.value.kind == "invalid_input"
def test_validate_model() -> None:
assert validate_model("anthropic/claude-haiku-4-5") == "anthropic/claude-haiku-4-5"
with pytest.raises(PiCodingError) as bad:
validate_model("bad model")
assert bad.value.kind == "invalid_input"
with patch.dict(os.environ, {"PI_CODING_MODEL": ""}, clear=False):
assert validate_model("") is None
# --------------------------------------------------------------------------- #
# run() lifecycle + error_kind
# --------------------------------------------------------------------------- #
def test_run_disabled() -> None:
with patch.dict(os.environ, {}, clear=False):
os.environ.pop("PI_CODING_ENABLED", None)
out = pi_coding_task.run(task="do something")
assert out["success"] is False
assert out["error_kind"] == "disabled"
def test_run_invalid_task() -> None:
with patch.dict(os.environ, {"PI_CODING_ENABLED": "1"}, clear=False):
out = pi_coding_task.run(task=" ")
assert out["success"] is False
assert out["error_kind"] == "invalid_input"
@patch(_VERIFY, return_value=(False, "pi not installed"))
def test_run_cli_unavailable(_mock_verify: MagicMock, tmp_path: Path) -> None:
with patch.dict(os.environ, {"PI_CODING_ENABLED": "1"}, clear=False):
out = pi_coding_task.run(task="fix it", workspace=str(tmp_path))
assert out["success"] is False
assert out["error_kind"] == "cli_unavailable"
@patch(_RUN)
@patch(_VERIFY, return_value=(True, "ok"))
def test_run_success(_mock_verify: MagicMock, mock_run: MagicMock, tmp_path: Path) -> None:
mock_run.return_value = PiCodingResult(
success=True,
summary="edited foo.py",
changed_files=["foo.py"],
diff="diff --git a/foo.py b/foo.py\n",
returncode=0,
)
with patch.dict(os.environ, {"PI_CODING_ENABLED": "1"}, clear=False):
out = pi_coding_task.run(
task="fix it", workspace=str(tmp_path), model="groq/llama-3.1-8b-instant"
)
assert out["success"] is True
assert out["error_kind"] is None
assert out["changed_files"] == ["foo.py"]
assert "diff --git" in out["diff"]
kwargs = mock_run.call_args.kwargs
assert kwargs["workspace"] == str(tmp_path)
assert kwargs["model"] == "groq/llama-3.1-8b-instant"
@patch(_RUN)
@patch(_VERIFY, return_value=(True, "ok"))
def test_run_error_kinds(_mock_verify: MagicMock, mock_run: MagicMock, tmp_path: Path) -> None:
mock_run.return_value = PiCodingResult(
success=False, summary="", error="pi timed out", timed_out=True
)
with patch.dict(os.environ, {"PI_CODING_ENABLED": "1"}, clear=False):
out = pi_coding_task.run(task="fix it", workspace=str(tmp_path))
assert out["error_kind"] == "timeout"
mock_run.return_value = PiCodingResult(success=False, summary="", error="model not found")
with patch.dict(os.environ, {"PI_CODING_ENABLED": "1"}, clear=False):
out = pi_coding_task.run(task="fix it", workspace=str(tmp_path))
assert out["error_kind"] == "execution_error"
@patch(_VERIFY, return_value=(True, "ok"))
@patch(_RUN, side_effect=RuntimeError("boom"))
def test_run_unexpected_exception_returns_error_dict(
_mock_run: MagicMock, _mock_verify: MagicMock, tmp_path: Path
) -> None:
# __call__ wraps run(); an unexpected exception is reported + degraded to a dict.
with patch.dict(os.environ, {"PI_CODING_ENABLED": "1"}, clear=False):
out = pi_coding_task(task="fix it", workspace=str(tmp_path))
assert "error" in out
# --------------------------------------------------------------------------- #
# registry discovery
# --------------------------------------------------------------------------- #
def test_registry_discovers_pi_coding_on_investigation_surface() -> None:
from tools.registry import get_registered_tool_map
investigation = get_registered_tool_map("investigation")
chat = get_registered_tool_map("chat")
assert "pi_coding_task" in investigation
assert "pi_coding_task" not in chat
rt = investigation["pi_coding_task"]
assert rt.requires_approval is False
assert rt.side_effect_level == "mutating"
def test_tool_subclass_constructs() -> None:
assert isinstance(pi_coding_task, PiCodingTool)