Files
opensquilla--opensquilla/tests/test_cli/test_cli_product_completeness.py
2026-07-13 13:12:33 +08:00

1121 lines
38 KiB
Python

from __future__ import annotations
import json
import tomllib
from pathlib import Path
from typing import Any
from typer.testing import CliRunner
from opensquilla.cli.main import app
runner = CliRunner()
class FakeGatewayClient:
calls: list[tuple[str, Any]] = []
rpc_payloads: dict[str, Any] = {}
model_rows: list[dict[str, Any]] = []
sessions_payload: dict[str, Any] = {"sessions": [], "count": 0}
cost_payload: dict[str, Any] = {"breakdown": [], "totalCostUsd": 0.0}
async def connect(self, url: str, *, token=None) -> None:
type(self).calls.append(("connect", url))
async def close(self) -> None:
type(self).calls.append(("close", None))
async def call(self, method: str, params: dict | None = None) -> Any:
type(self).calls.append((method, params or {}))
return type(self).rpc_payloads.get(method, {})
async def list_models(
self,
provider: str | None = None,
capabilities: list[str] | None = None,
) -> list[dict[str, Any]]:
type(self).calls.append(
("models.list", {"provider": provider, "capabilities": capabilities})
)
return type(self).model_rows
async def list_sessions(self, limit: int = 50) -> dict[str, Any]:
type(self).calls.append(("sessions.list", {"limit": limit}))
return type(self).sessions_payload
async def resolve_session(self, key: str) -> dict[str, Any]:
type(self).calls.append(("sessions.resolve", {"key": key}))
return type(self).rpc_payloads.get("sessions.resolve", {"key": key})
async def preview_sessions(
self,
keys: list[str] | None = None,
limit: int = 50,
) -> dict[str, Any]:
type(self).calls.append(("sessions.preview", {"keys": keys, "limit": limit}))
return type(self).rpc_payloads.get("sessions.preview", {"previews": []})
async def abort_session(self, key: str) -> dict[str, Any]:
type(self).calls.append(("sessions.abort", {"key": key}))
return type(self).rpc_payloads.get("sessions.abort", {"aborted": False, "key": key})
async def usage_cost(self) -> dict[str, Any]:
type(self).calls.append(("usage.cost", {}))
return type(self).cost_payload
class FailingConnectGatewayClient(FakeGatewayClient):
async def connect(self, url: str, *, token=None) -> None:
raise SystemExit("gateway offline")
class RPCFailGatewayClient(FakeGatewayClient):
async def call(self, method: str, params: dict | None = None) -> Any:
from opensquilla.cli.gateway_client import GatewayRPCError
type(self).calls.append((method, params or {}))
raise GatewayRPCError(
method,
code="UNAUTHORIZED",
message="operator.admin scope required",
data={"scope": "operator.admin"},
)
def _install_fake_gateway(monkeypatch, cls=FakeGatewayClient) -> type[FakeGatewayClient]:
cls.calls = []
cls.rpc_payloads = {}
cls.model_rows = []
cls.sessions_payload = {"sessions": [], "count": 0}
cls.cost_payload = {"breakdown": [], "totalCostUsd": 0.0}
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", cls)
return cls
def test_catalog_list_json_surfaces(tmp_path: Path, monkeypatch):
target = tmp_path / "c.toml"
monkeypatch.setenv("OPENSQUILLA_GATEWAY_CONFIG_PATH", str(target))
runner.invoke(
app,
[
"channels", "add", "slack",
"--name", "w", "--token", "supersecret",
"--field", "signing_secret=ss",
],
)
providers = runner.invoke(app, ["providers", "list", "--json"])
search = runner.invoke(app, ["search", "list", "--json"])
channels = runner.invoke(app, ["channels", "list", "--json"])
assert providers.exit_code == 0, providers.stdout
assert search.exit_code == 0, search.stdout
assert channels.exit_code == 0, channels.stdout
assert any(row["providerId"] == "openrouter" for row in json.loads(providers.stdout))
assert any(row["providerId"] == "brave" for row in json.loads(search.stdout))
channel_payload = json.loads(channels.stdout)
assert channel_payload[0]["name"] == "w"
assert "supersecret" not in channels.stdout
assert "***" in channels.stdout
def test_models_list_json_uses_gateway_client(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"models.list": {
"models": [
{
"id": "model-a",
"provider": "openrouter",
"contextWindow": 123,
"capabilities": ["chat"],
}
],
"errors": [],
}
}
result = runner.invoke(app, ["models", "list", "--provider", "openrouter", "--json"])
assert result.exit_code == 0, result.stdout
assert json.loads(result.stdout)[0]["id"] == "model-a"
assert ("models.list", {"provider": "openrouter", "capabilities": None}) in fake.calls
def test_models_list_table_warns_about_provider_listing_errors(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"models.list": {
"models": [],
"errors": [
{
"provider": "openrouter",
"kind": "auth_invalid",
"detail": "401 invalid api key ***",
}
],
}
}
result = runner.invoke(app, ["models", "list"])
assert result.exit_code == 0, result.stdout
# Rows table stays on stdout; the warning block goes to stderr.
assert "auth_invalid" not in result.stdout
assert "openrouter" in result.stderr
assert "auth_invalid" in result.stderr
def test_config_get_honors_env_path_and_redacts(tmp_path: Path, monkeypatch):
target = tmp_path / "opensquilla.toml"
target.write_text(
"search_api_key = \"secret\"\n"
"[llm]\nprovider = \"openrouter\"\nmodel = \"test/model\"\n",
encoding="utf-8",
)
monkeypatch.setenv("OPENSQUILLA_GATEWAY_CONFIG_PATH", str(target))
model_result = runner.invoke(app, ["config", "get", "llm.model"])
key_result = runner.invoke(app, ["config", "get", "search_api_key"])
all_result = runner.invoke(app, ["config", "get"])
assert model_result.exit_code == 0, model_result.stdout
assert "test/model" in model_result.stdout
assert key_result.exit_code == 0, key_result.stdout
assert "[redacted]" in key_result.stdout
assert "secret" not in key_result.stdout
assert all_result.exit_code == 0, all_result.stdout
assert "[redacted]" in all_result.stdout
assert "secret" not in all_result.stdout
def test_config_get_explicit_config_path_wins(tmp_path: Path):
target = tmp_path / "explicit.toml"
target.write_text("[llm]\nmodel = \"explicit/model\"\n", encoding="utf-8")
result = runner.invoke(app, ["config", "get", "llm.model", "--config", str(target)])
assert result.exit_code == 0, result.stdout
assert "explicit/model" in result.stdout
def test_config_set_explicit_config_path_persists_to_target(tmp_path: Path):
target = tmp_path / "explicit.toml"
target.write_text("log_file_enabled = false\n", encoding="utf-8")
result = runner.invoke(
app,
["config", "set", "log_file_enabled", "true", "--config", str(target)],
)
assert result.exit_code == 0, result.stdout
assert "Config:" in result.stdout
assert "Restart the gateway" in result.stdout
check = runner.invoke(app, ["config", "get", "log_file_enabled", "--config", str(target)])
assert check.exit_code == 0, check.stdout
assert "True" in check.stdout
def test_config_set_get_privacy_network_observability_round_trips(tmp_path: Path):
target = tmp_path / "privacy.toml"
target.write_text("", encoding="utf-8")
result = runner.invoke(
app,
[
"config",
"set",
"privacy.disable_network_observability",
"true",
"--config",
str(target),
],
)
assert result.exit_code == 0, result.stdout
persisted = tomllib.loads(target.read_text(encoding="utf-8"))
assert persisted["privacy"]["disable_network_observability"] is True
check = runner.invoke(
app,
[
"config",
"get",
"privacy.disable_network_observability",
"--config",
str(target),
],
)
assert check.exit_code == 0, check.stdout
assert "True" in check.stdout
def test_version_check_honors_config_privacy_network_observability(
tmp_path: Path,
monkeypatch,
):
from opensquilla.observability import update_check
target = tmp_path / "privacy.toml"
target.write_text(
"[privacy]\ndisable_network_observability = true\n",
encoding="utf-8",
)
monkeypatch.setenv("OPENSQUILLA_GATEWAY_CONFIG_PATH", str(target))
monkeypatch.setattr(update_check, "_CACHED_INFO", None)
def fail_fetch(*_args, **_kwargs):
raise AssertionError("version --check should not contact the network")
monkeypatch.setattr(update_check, "_fetch_latest_release", fail_fetch)
result = runner.invoke(app, ["version", "--check", "--json"])
assert result.exit_code == 0, result.stdout
payload = json.loads(result.stdout)
assert payload["latest"] is None
assert payload["updateAvailable"] is False
assert payload["disabled"] is True
assert payload["error"] is None
def test_gateway_json_errors_go_to_stderr(monkeypatch):
_install_fake_gateway(monkeypatch, FailingConnectGatewayClient)
result = runner.invoke(app, ["models", "list", "--json"])
assert result.exit_code == 1
assert result.stdout == ""
assert json.loads(result.stderr)["error"]["code"] == "GATEWAY_UNAVAILABLE"
def test_skills_view_and_update_use_gateway_rpc(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"skills.get": {
"name": "planner",
"layer": "managed",
"eligible": True,
"description": "Plan work",
"content": "skill body",
},
"skills.update": {
"results": [{"success": True, "name": "planner", "message": "updated"}]
},
}
view = runner.invoke(app, ["skills", "view", "planner", "--json"])
update = runner.invoke(app, ["skills", "update", "planner", "--json"])
assert view.exit_code == 0, view.stdout
assert json.loads(view.stdout)["name"] == "planner"
assert update.exit_code == 0, update.stdout
assert json.loads(update.stdout)["results"][0]["success"] is True
assert ("skills.get", {"name": "planner"}) in fake.calls
assert ("skills.update", {"name": "planner"}) in fake.calls
def test_skills_update_all_exits_nonzero_on_partial_failure(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"skills.update": {
"results": [
{"success": True, "name": "a", "message": "updated"},
{"success": False, "name": "b", "message": "failed"},
]
}
}
result = runner.invoke(app, ["skills", "update", "--all", "--json"])
assert result.exit_code == 1
assert json.loads(result.stdout)["results"][1]["name"] == "b"
assert ("skills.update", {}) in fake.calls
def test_skills_update_exits_nonzero_on_top_level_failure(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"skills.update": {
"success": False,
"message": "No skill installer configured",
}
}
result = runner.invoke(app, ["skills", "update", "planner", "--json"])
assert result.exit_code == 1
assert json.loads(result.stdout)["message"] == "No skill installer configured"
assert ("skills.update", {"name": "planner"}) in fake.calls
def test_skills_install_and_uninstall_use_gateway_rpc_when_available(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"skills.install": {
"success": True,
"name": "planner",
"message": "installed by gateway",
"path": "/gateway/skill",
},
"skills.uninstall": {
"success": True,
"name": "planner",
"message": "removed by gateway",
},
}
install = runner.invoke(app, ["skills", "install", "planner", "--json"])
uninstall = runner.invoke(app, ["skills", "uninstall", "planner", "--json"])
assert install.exit_code == 0, install.stdout
assert json.loads(install.stdout)["path"] == "/gateway/skill"
assert uninstall.exit_code == 0, uninstall.stdout
assert json.loads(uninstall.stdout)["message"] == "removed by gateway"
assert (
"skills.install",
{"identifier": "planner", "source": "clawhub", "force": False},
) in fake.calls
assert ("skills.uninstall", {"name": "planner"}) in fake.calls
def test_skills_install_and_uninstall_fall_back_when_gateway_unavailable(monkeypatch):
_install_fake_gateway(monkeypatch, FailingConnectGatewayClient)
from opensquilla.skills.hub.installer import InstallResult, SkillInstaller
async def fake_install(self, identifier: str, source: str, force: bool = False):
return InstallResult(
success=True,
name=identifier,
message=f"installed from {source}",
path="/tmp/skill",
)
async def fake_uninstall(self, name: str):
return InstallResult(success=False, name=name, message="missing")
monkeypatch.setattr(SkillInstaller, "install", fake_install)
monkeypatch.setattr(SkillInstaller, "uninstall", fake_uninstall)
install = runner.invoke(app, ["skills", "install", "planner", "--json"])
uninstall = runner.invoke(app, ["skills", "uninstall", "missing", "--json"])
assert install.exit_code == 0, install.stdout
assert json.loads(install.stdout)["path"] == "/tmp/skill"
assert uninstall.exit_code == 1
assert json.loads(uninstall.stdout)["message"] == "missing"
def test_skills_install_fallback_exposes_github_source_without_token(monkeypatch):
_install_fake_gateway(monkeypatch, FailingConnectGatewayClient)
monkeypatch.delenv("GITHUB_TOKEN", raising=False)
from opensquilla.skills.hub.installer import InstallResult, SkillInstaller
async def fake_install(self, identifier: str, source: str, force: bool = False):
assert source == "github"
assert identifier == "https://github.com/acme/skillpack/tree/main/skills/demo"
assert "github" in self._router.source_ids
return InstallResult(
success=True,
name="demo",
message="installed from github",
path="/tmp/demo",
)
monkeypatch.setattr(SkillInstaller, "install", fake_install)
install = runner.invoke(
app,
[
"skills",
"install",
"https://github.com/acme/skillpack/tree/main/skills/demo",
"--source",
"github",
"--json",
],
)
assert install.exit_code == 0, install.stdout
assert json.loads(install.stdout)["name"] == "demo"
def test_skills_install_rpc_error_does_not_fall_back_to_local_installer(monkeypatch):
fake = _install_fake_gateway(monkeypatch, RPCFailGatewayClient)
from opensquilla.skills.hub.installer import SkillInstaller
local_install_called = False
async def fake_install(self, identifier: str, source: str, force: bool = False):
nonlocal local_install_called
local_install_called = True
raise AssertionError("local fallback must not run after RPC errors")
monkeypatch.setattr(SkillInstaller, "install", fake_install)
result = runner.invoke(app, ["skills", "install", "planner", "--json"])
assert result.exit_code == 1
assert result.stdout == ""
assert json.loads(result.stderr)["error"]["code"] == "UNAUTHORIZED"
assert local_install_called is False
assert (
"skills.install",
{"identifier": "planner", "source": "clawhub", "force": False},
) in fake.calls
def test_sessions_list_json_filters_client_side(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.sessions_payload = {
"sessions": [
{
"key": "a",
"agentId": "main",
"status": "active",
"channel": "slack",
"updatedAt": "2026-05-05T00:00:00Z",
"message_count": 2,
},
{
"key": "b",
"agentId": "ops",
"status": "done",
"channel": "telegram",
"updatedAt": "2026-05-01T00:00:00Z",
"message_count": 1,
},
],
"count": 2,
"ts": 1,
}
result = runner.invoke(
app,
[
"sessions",
"list",
"--agent",
"main",
"--status",
"active",
"--channel",
"slack",
"--since",
"2026-05-04",
"--json",
],
)
assert result.exit_code == 0, result.stdout
payload = json.loads(result.stdout)
assert payload["count"] == 1
assert payload["sessions"][0]["key"] == "a"
def test_sessions_show_json_resolves_and_previews(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"sessions.resolve": {
"session_key": "agent:main:abc",
"session_id": "abc",
"status": "active",
"agent_id": "main",
"model": "openai/test",
},
"sessions.preview": {
"previews": [
{
"key": "agent:main:abc",
"title": "Debugging",
"lastMessage": "latest",
"updatedAt": 123,
}
]
},
}
result = runner.invoke(app, ["sessions", "show", "abc", "--json"])
assert result.exit_code == 0, result.stdout
payload = json.loads(result.stdout)
assert payload["resolved"]["session_key"] == "agent:main:abc"
assert payload["preview"]["previews"][0]["lastMessage"] == "latest"
assert ("sessions.resolve", {"key": "abc"}) in fake.calls
assert ("sessions.preview", {"keys": ["agent:main:abc"], "limit": 50}) in fake.calls
def test_sessions_show_json_errors_go_to_stderr(monkeypatch):
_install_fake_gateway(monkeypatch, FailingConnectGatewayClient)
result = runner.invoke(app, ["sessions", "show", "abc", "--json"])
assert result.exit_code == 1
assert result.stdout == ""
assert json.loads(result.stderr)["error"]["code"] == "GATEWAY_UNAVAILABLE"
def test_sessions_abort_resolves_then_aborts(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"sessions.resolve": {"key": "agent:main:abc", "session_id": "abc"},
"sessions.abort": {"aborted": True, "key": "agent:main:abc"},
}
result = runner.invoke(app, ["sessions", "abort", "abc", "--json"])
assert result.exit_code == 0, result.stdout
payload = json.loads(result.stdout)
assert payload["aborted"] is True
assert ("sessions.resolve", {"key": "abc"}) in fake.calls
assert ("sessions.abort", {"key": "agent:main:abc"}) in fake.calls
def test_memory_status_json_reuses_doctor_rpc(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"doctor.memory.status": {
"backend": "sqlite",
"status": "ok",
"entryCount": 3,
"sizeBytes": 42,
"error": None,
}
}
result = runner.invoke(app, ["memory", "status", "--agent", "main", "--json"])
assert result.exit_code == 0, result.stdout
assert json.loads(result.stdout)["entryCount"] == 3
assert ("doctor.memory.status", {"agentId": "main"}) in fake.calls
def test_memory_status_table_surfaces_source_counts(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"doctor.memory.status": {
"backend": "sqlite",
"status": "ok",
"entryCount": 4,
"sizeBytes": 42,
"error": None,
"sourceCounts": {
"memory": {"files": 1, "chunks": 2},
"sessions": {"files": 1, "chunks": 2},
},
}
}
result = runner.invoke(app, ["memory", "status", "--agent", "main"])
assert result.exit_code == 0, result.stdout
assert "Sources" in result.stdout
assert "memory" in result.stdout
assert "sessions" in result.stdout
def test_memory_status_deep_json_passes_deep_flag(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"doctor.memory.status": {
"backend": "sqlite",
"status": "degraded",
"vecAvailable": False,
"ftsAvailable": True,
"degraded": [],
}
}
result = runner.invoke(app, ["memory", "status", "--agent", "main", "--deep", "--json"])
assert result.exit_code == 0, result.stdout
assert json.loads(result.stdout)["vecAvailable"] is False
assert ("doctor.memory.status", {"agentId": "main", "deep": True}) in fake.calls
def test_memory_list_json_uses_gateway_rpc(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"memory.list": {
"agentId": "main",
"count": 1,
"files": [{"path": "memory/a.md", "lineCount": 2, "sizeBytes": 12}],
}
}
result = runner.invoke(app, ["memory", "list", "--agent", "main", "--json"])
assert result.exit_code == 0, result.stdout
assert json.loads(result.stdout)["files"][0]["path"] == "memory/a.md"
assert ("memory.list", {"agentId": "main"}) in fake.calls
def test_memory_search_and_show_use_gateway_rpcs(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"memory.search": {
"agentId": "main",
"query": "alpha",
"count": 1,
"results": [
{
"source": "sessions",
"path": "sessions/main/session-1.md",
"startLine": 1,
"endLine": 2,
"score": 0.8,
"snippet": "alpha transcript",
}
],
},
"memory.show": {
"agentId": "main",
"path": "memory/a.md",
"fromLine": 2,
"lineCount": 1,
"truncated": False,
"content": "line",
},
}
search_default = runner.invoke(
app,
["memory", "search", "alpha", "--limit", "3", "--json"],
)
search = runner.invoke(
app,
["memory", "search", "alpha", "--limit", "3", "--source", "sessions", "--json"],
)
search_table = runner.invoke(
app,
["memory", "search", "alpha", "--limit", "3", "--source", "sessions"],
)
show = runner.invoke(
app,
[
"memory",
"show",
"memory/a.md",
"--from-line",
"2",
"--lines",
"1",
"--json",
],
)
assert search_default.exit_code == 0, search_default.stdout
assert search.exit_code == 0, search.stdout
assert search_table.exit_code == 0, search_table.stdout
assert show.exit_code == 0, show.stdout
assert "Source" in search_table.stdout
assert "sessions" in search_table.stdout
assert json.loads(show.stdout)["content"] == "line"
assert (
"memory.search",
{"query": "alpha", "agentId": "main", "limit": 3, "source": "memory"},
) in fake.calls
assert (
"memory.search",
{"query": "alpha", "agentId": "main", "limit": 3, "source": "sessions"},
) in fake.calls
assert (
"memory.show",
{"path": "memory/a.md", "agentId": "main", "fromLine": 2, "lines": 1},
) in fake.calls
def test_memory_index_raw_fallback_and_repair_commands_use_admin_rpcs(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"memory.index": {"agentId": "main", "force": True},
"memory.raw_fallbacks.list": {
"agentId": "main",
"count": 1,
"files": [{"path": "memory/.raw_fallbacks/raw.md", "sizeBytes": 12}],
},
"memory.raw_fallbacks.show": {
"agentId": "main",
"path": "memory/.raw_fallbacks/raw.md",
"fromLine": 1,
"lineCount": 1,
"truncated": False,
"content": "raw",
},
"memory.repair.list": {
"agentId": "main",
"count": 1,
"items": [
{
"summaryId": 7,
"sessionKey": "agent:main:thread-1",
"compactionId": "cmp-1",
"flushReceiptStatus": "degraded_forensic",
}
],
},
"memory.repair.show": {
"agentId": "main",
"sessionKey": "agent:main:thread-1",
"compactionId": "cmp-1",
"entries": [{"role": "user", "content": "preimage fact"}],
},
"memory.repair.run": {
"agentId": "main",
"count": 1,
"results": [{"compactionId": "cmp-1", "status": "repaired"}],
},
}
index = runner.invoke(app, ["memory", "index", "--agent", "main", "--force", "--json"])
listed = runner.invoke(app, ["memory", "raw-fallbacks", "list", "--json"])
shown = runner.invoke(
app,
["memory", "raw-fallbacks", "show", "memory/.raw_fallbacks/raw.md", "--json"],
)
repair_listed = runner.invoke(app, ["memory", "repair", "list", "--json"])
repair_shown = runner.invoke(
app,
[
"memory",
"repair",
"show",
"--session-key",
"agent:main:thread-1",
"--compaction-id",
"cmp-1",
"--json",
],
)
repair_run = runner.invoke(
app,
[
"memory",
"repair",
"run",
"--session-key",
"agent:main:thread-1",
"--compaction-id",
"cmp-1",
"--json",
],
)
assert index.exit_code == 0, index.stdout
assert listed.exit_code == 0, listed.stdout
assert shown.exit_code == 0, shown.stdout
assert repair_listed.exit_code == 0, repair_listed.stdout
assert repair_shown.exit_code == 0, repair_shown.stdout
assert repair_run.exit_code == 0, repair_run.stdout
assert ("memory.index", {"agentId": "main", "force": True}) in fake.calls
assert ("memory.raw_fallbacks.list", {"agentId": "main"}) in fake.calls
assert (
"memory.raw_fallbacks.show",
{"path": "memory/.raw_fallbacks/raw.md", "agentId": "main"},
) in fake.calls
assert ("memory.repair.list", {"agentId": "main", "limit": 50}) in fake.calls
assert (
"memory.repair.show",
{
"agentId": "main",
"sessionKey": "agent:main:thread-1",
"compactionId": "cmp-1",
},
) in fake.calls
assert (
"memory.repair.run",
{
"agentId": "main",
"limit": 50,
"sessionKey": "agent:main:thread-1",
"compactionId": "cmp-1",
},
) in fake.calls
def test_cron_run_requires_confirmation_before_gateway_call(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
result = runner.invoke(app, ["cron", "run", "job-1", "--json"])
assert result.exit_code == 2
assert result.stdout == ""
assert json.loads(result.stderr)["error"]["code"] == "CONFIRMATION_REQUIRED"
assert fake.calls == []
def test_cron_run_yes_calls_existing_rpc(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {"cron.run": {"success": True, "status": "accepted"}}
result = runner.invoke(app, ["cron", "run", "job-1", "--yes", "--json"])
assert result.exit_code == 0, result.stdout
assert json.loads(result.stdout)["status"] == "accepted"
assert ("cron.run", {"id": "job-1"}) in fake.calls
def test_cron_commands_use_existing_rpc_payloads(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"cron.list": [{"id": "job-1", "name": "Daily", "agentId": "main"}],
"cron.status": {"id": "job-1", "name": "Daily"},
"cron.add": {"id": "job-2", "expression": "*/5 * * * *"},
"cron.update": {"id": "job-1", "enabled": False},
"cron.runs": [{"id": "run-1", "status": "ok"}],
}
list_result = runner.invoke(app, ["cron", "list", "--agent", "main", "--json"])
status_result = runner.invoke(app, ["cron", "status", "job-1", "--json"])
add_result = runner.invoke(
app,
[
"cron",
"add",
"--expression",
"*/5 * * * *",
"--text",
"check in",
"--agent",
"main",
"--session-target",
"isolated",
"--json",
],
)
update_result = runner.invoke(app, ["cron", "update", "job-1", "--disabled", "--json"])
remove_result = runner.invoke(app, ["cron", "remove", "job-1", "--yes", "--json"])
runs_result = runner.invoke(app, ["cron", "runs", "job-1", "--limit", "3", "--json"])
assert list_result.exit_code == 0, list_result.stdout
assert status_result.exit_code == 0, status_result.stdout
assert add_result.exit_code == 0, add_result.stdout
assert update_result.exit_code == 0, update_result.stdout
assert remove_result.exit_code == 0, remove_result.stdout
assert runs_result.exit_code == 0, runs_result.stdout
assert ("cron.list", {"agentId": "main"}) in fake.calls
assert ("cron.status", {"id": "job-1"}) in fake.calls
assert (
"cron.add",
{
"expression": "*/5 * * * *",
"text": "check in",
"payloadKind": "reminder",
"sessionTarget": "isolated",
"agentId": "main",
},
) in fake.calls
assert ("cron.update", {"id": "job-1", "enabled": False}) in fake.calls
assert ("cron.remove", {"id": "job-1"}) in fake.calls
assert ("cron.runs", {"id": "job-1", "limit": 3}) in fake.calls
def test_channels_runtime_restart_requires_confirmation(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
result = runner.invoke(app, ["channels", "restart", "slack", "--json"])
assert result.exit_code == 2
assert json.loads(result.stderr)["error"]["code"] == "CONFIRMATION_REQUIRED"
assert fake.calls == []
def test_channels_status_and_logout_use_existing_rpcs(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"channels.status": {
"channels": [{"name": "slack", "status": "connected", "connected": True}]
},
"channels.logout": {"status": "disconnected", "channel": "slack"},
}
status = runner.invoke(app, ["channels", "status", "slack", "--json"])
logout = runner.invoke(app, ["channels", "logout", "slack", "--yes", "--json"])
assert status.exit_code == 0, status.stdout
assert logout.exit_code == 0, logout.stdout
assert json.loads(status.stdout)["channels"][0]["status"] == "connected"
assert json.loads(logout.stdout)["status"] == "disconnected"
assert ("channels.status", {}) in fake.calls
assert ("channels.logout", {"name": "slack"}) in fake.calls
def test_channels_status_table_shows_channel_diagnostic(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"channels.status": {
"channels": [
{
"name": "dingtalk",
"type": "dingtalk",
"status": "stopped",
"connected": False,
"enabled": True,
"configured": True,
"diagnostics": {
"last_error": {
"error_class": "auth_invalid",
"message": "凭证无效:检查 DingTalk AppKey/AppSecret",
}
},
}
]
}
}
result = runner.invoke(app, ["channels", "status", "dingtalk"])
assert result.exit_code == 0, result.stdout
assert "凭证无效:检查 DingTalk AppKey/AppSecret" in result.stdout
assert "auth_invalid" not in result.stdout
assert ("channels.status", {}) in fake.calls
def test_runtime_diagnostics_commands_can_target_configured_gateway(
tmp_path: Path, monkeypatch
):
fake = _install_fake_gateway(monkeypatch)
target = tmp_path / "custom.toml"
target.write_text('host = "0.0.0.0"\nport = 19999\n', encoding="utf-8")
fake.rpc_payloads = {
"channels.status": {
"channels": [{"name": "slack", "status": "connected", "connected": True}]
},
"providers.status": {"activeProvider": "openrouter", "providers": [], "count": 0},
"search.status": {
"activeProvider": "duckduckgo",
"provider": "duckduckgo",
"configured": True,
"buildable": True,
},
"diagnostics.status": {"diagnostics_enabled": {"effective": True}},
"doctor.memory.status": {"backend": "sqlite", "status": "ok"},
}
channels = runner.invoke(
app, ["channels", "status", "slack", "--json", "--config", str(target)]
)
providers = runner.invoke(app, ["providers", "status", "--json", "--config", str(target)])
search = runner.invoke(app, ["search", "status", "--json", "--config", str(target)])
diagnostics = runner.invoke(
app, ["diagnostics", "status", "--json", "--config", str(target)]
)
memory = runner.invoke(app, ["memory", "status", "--json", "--config", str(target)])
assert channels.exit_code == 0, channels.stdout
assert providers.exit_code == 0, providers.stdout
assert search.exit_code == 0, search.stdout
assert diagnostics.exit_code == 0, diagnostics.stdout
assert memory.exit_code == 0, memory.stdout
connected_urls = [value for method, value in fake.calls if method == "connect"]
assert connected_urls == ["ws://127.0.0.1:19999/ws"] * 5
def test_runtime_diagnostics_commands_use_gateway_config_env_path(
tmp_path: Path, monkeypatch
):
fake = _install_fake_gateway(monkeypatch)
target = tmp_path / "env-config.toml"
target.write_text('host = "127.0.0.1"\nport = 20001\n', encoding="utf-8")
monkeypatch.setenv("OPENSQUILLA_GATEWAY_CONFIG_PATH", str(target))
fake.rpc_payloads = {
"providers.status": {"activeProvider": "openrouter", "providers": [], "count": 0},
}
result = runner.invoke(app, ["providers", "status", "--json"])
assert result.exit_code == 0, result.stdout
assert ("connect", "ws://127.0.0.1:20001/ws") in fake.calls
def test_cost_json_returns_gateway_payload(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.cost_payload = {
"breakdown": [{"session": "s", "model": "m", "input_tokens": 1, "cost_usd": 0.1}],
"totalCostUsd": 0.1,
}
result = runner.invoke(app, ["cost", "--json"])
assert result.exit_code == 0, result.stdout
assert json.loads(result.stdout)["totalCostUsd"] == 0.1
assert ("usage.cost", {}) in fake.calls
def test_provider_and_search_diagnostics_use_gateway_rpcs(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"providers.status": {"activeProvider": "openrouter", "providers": [], "count": 0},
"search.status": {
"activeProvider": "duckduckgo",
"provider": "duckduckgo",
"configured": True,
"buildable": True,
},
"search.query": {
"ok": True,
"query": "hello",
"provider": "duckduckgo",
"results": [{"title": "T", "url": "https://example.com", "snippet": "S"}],
},
}
providers = runner.invoke(app, ["providers", "status", "--json"])
search_status = runner.invoke(app, ["search", "status", "--json"])
search_query = runner.invoke(
app,
["search", "query", "hello", "--provider", "duckduckgo", "--limit", "2", "--json"],
)
assert providers.exit_code == 0, providers.stdout
assert search_status.exit_code == 0, search_status.stdout
assert search_query.exit_code == 0, search_query.stdout
assert json.loads(search_query.stdout)["results"][0]["title"] == "T"
assert ("providers.status", {"probeModels": False}) in fake.calls
assert ("search.status", {}) in fake.calls
assert (
"search.query",
{"query": "hello", "provider": "duckduckgo", "limit": 2},
) in fake.calls
def test_search_query_json_exits_nonzero_on_diagnostic_failure(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"search.query": {
"ok": False,
"query": "hello",
"provider": "duckduckgo",
"results": [],
"error": {
"kind": "network",
"class": "ConnectError",
"message": "network down",
"retryable": True,
},
}
}
result = runner.invoke(app, ["search", "query", "hello", "--json"])
assert result.exit_code == 1
assert json.loads(result.stdout)["error"]["message"] == "network down"
def test_doctor_json_uses_gateway_doctor_rpc(monkeypatch):
fake = _install_fake_gateway(monkeypatch)
fake.rpc_payloads = {
"doctor.status": {
"status": "ready",
"ready": True,
"summary": "Ready",
"counts": {"error": 0, "warn": 0, "info": 0, "ok": 1},
"findings": [],
}
}
result = runner.invoke(app, ["doctor", "--json"])
assert result.exit_code == 0, result.stdout
assert json.loads(result.stdout)["ready"] is True
assert ("doctor.status", {"agentId": "main", "deep": True}) in fake.calls