mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-30 20:35:22 +08:00
121 lines
4.8 KiB
Python
121 lines
4.8 KiB
Python
"""Real SDK runtime contracts; vendor services are explicitly local fixtures.
|
|
|
|
Install this checkout's plugin requirements and the intended SDK before running.
|
|
No SDK modules are stubbed. Each plugin runs in a fresh interpreter, matching
|
|
production module isolation. Optional RUNNER_EVIDENCE_DIR retains raw RPC logs.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
CODING_PLUGINS = {"acp-agent-runner", "claude-code-agent", "codex-agent"}
|
|
PLUGINS = {
|
|
"acp-agent-runner": "ACPAgentRunner", # Published identity survives the Runner component-kind rename.
|
|
"claude-code-agent": "ClaudeCodeAgent",
|
|
"codex-agent": "CodexAgent",
|
|
"coze-agent": "CozeAgent",
|
|
"dashscope-agent": "DashScopeAgent",
|
|
"deerflow-agent": "DeerFlowAgent",
|
|
"dify-agent": "DifyAgent",
|
|
"langflow-agent": "LangflowAgent",
|
|
"n8n-agent": "N8nAgent",
|
|
"tbox-agent": "TboxAgent",
|
|
"weknora-agent": "WeKnoraAgent",
|
|
}
|
|
|
|
|
|
def plugin_python(plugin):
|
|
# Coding payloads still pin b3. Verify their real RPC/dependencies there,
|
|
# while aggregate compatibility and the HTTP release use the b5 environment.
|
|
return os.environ.get("CODING_RUNNER_TEST_PYTHON", sys.executable) if plugin in CODING_PLUGINS else sys.executable
|
|
|
|
|
|
@pytest.fixture(scope="module", params=sorted(PLUGINS))
|
|
def runtime_evidence(request, tmp_path_factory):
|
|
plugin = request.param
|
|
evidence_dir = Path(os.environ.get("RUNNER_EVIDENCE_DIR") or tmp_path_factory.mktemp("runtime-evidence"))
|
|
evidence_dir.mkdir(parents=True, exist_ok=True)
|
|
destination = evidence_dir / f"{plugin}.json"
|
|
env = os.environ.copy()
|
|
# Do not inherit provider credentials, proxy configuration, or daemon switches.
|
|
for key in list(env):
|
|
if any(token in key.upper() for token in ("API_KEY", "TOKEN", "SECRET", "PROXY", "DAEMON")):
|
|
env.pop(key)
|
|
env["NO_PROXY"] = "127.0.0.1,localhost"
|
|
result = subprocess.run(
|
|
[plugin_python(plugin), str(ROOT / "tests/fixtures/real_sdk_runtime_probe.py"), str(destination)],
|
|
cwd=ROOT / plugin,
|
|
env=env,
|
|
text=True,
|
|
capture_output=True,
|
|
timeout=40,
|
|
)
|
|
(evidence_dir / f"{plugin}.log").write_text(result.stdout + result.stderr)
|
|
assert result.returncode == 0, result.stdout + result.stderr
|
|
return plugin, json.loads(destination.read_text())
|
|
|
|
|
|
def test_real_sdk_discovers_and_initializes_runner(runtime_evidence):
|
|
plugin, evidence = runtime_evidence
|
|
assert Path(evidence["sdk_path"]).parts[-2:] == ("langbot_plugin", "__init__.py")
|
|
assert evidence["identity"] == f"langbot-team/{PLUGINS[plugin]}"
|
|
assert evidence["status"] == "initialized"
|
|
assert evidence["component_kind"] == "Runner"
|
|
assert evidence["runner_class"] == "DefaultRunner"
|
|
assert evidence["transport"] == "real SDK WebSocketConnection + PluginRuntimeHandler on loopback"
|
|
|
|
|
|
def test_real_sdk_dispatches_config_failure(runtime_evidence):
|
|
_, evidence = runtime_evidence
|
|
results = evidence["invalid_results"]
|
|
assert [item["type"] for item in results] == ["run.failed"]
|
|
assert results[0]["data"]["code"].endswith(".config_invalid"), results
|
|
assert results[0]["sequence"] == 1
|
|
|
|
|
|
def test_real_sdk_dispatches_local_vendor_fixture(runtime_evidence):
|
|
_, evidence = runtime_evidence
|
|
results = evidence["fixture_results"]
|
|
assert results[-1]["type"] == "run.completed", results
|
|
assert not any(item["type"] == "run.failed" for item in results), results
|
|
assert [item["sequence"] for item in results] == list(range(1, len(results) + 1))
|
|
assert all(item["run_id"] == "fixture-run" for item in results)
|
|
assert "FIXTURE_OK" in json.dumps(results), results
|
|
assert any(item["type"] == "state.updated" for item in results), results
|
|
assert evidence["external_live_connectivity_tested"] is False
|
|
assert evidence["fixture_calls"], evidence
|
|
|
|
|
|
@pytest.mark.parametrize("plugin", sorted(PLUGINS))
|
|
def test_installed_real_dependencies_satisfy_plugin_requirements(plugin):
|
|
# Resolve every declared dependency inside the same interpreter used by RPC.
|
|
code = """
|
|
import importlib.metadata
|
|
from pathlib import Path
|
|
from packaging.requirements import Requirement
|
|
for line in Path("requirements.txt").read_text().splitlines():
|
|
if not line.strip() or line.startswith("#"):
|
|
continue
|
|
requirement = Requirement(line)
|
|
if requirement.marker and not requirement.marker.evaluate():
|
|
continue
|
|
version = importlib.metadata.version(requirement.name)
|
|
assert version in requirement.specifier, f"{requirement.name}=={version} violates {line}"
|
|
"""
|
|
result = subprocess.run(
|
|
[plugin_python(plugin), "-c", code],
|
|
cwd=ROOT / plugin,
|
|
text=True,
|
|
capture_output=True,
|
|
timeout=30,
|
|
)
|
|
assert result.returncode == 0, f"{plugin}: {result.stdout}{result.stderr}"
|