Files
2026-07-13 13:12:33 +08:00

1139 lines
41 KiB
Python

from __future__ import annotations
import json
import shlex
from typing import Any
from typer.testing import CliRunner
from opensquilla.cli.main import app
runner = CliRunner()
def _config_arg(path: Any) -> str:
return shlex.quote(str(path))
class _FakeGatewayClient:
calls: list[tuple[str, Any]] = []
payload: dict[str, Any] = {
"status": "action_required",
"ready": False,
"summary": "1 action required",
"counts": {"error": 1, "warn": 0, "info": 0, "ok": 1},
"findings": [
{
"id": "provider.active.not_configured",
"severity": "error",
"surface": "provider",
"title": "Active provider is not configured",
"detail": "openrouter is active but missing required configuration.",
"evidence": {"providerId": "openrouter"},
"fixSteps": [
{
"label": "Configure provider",
"command": (
"opensquilla providers configure openrouter "
"--api-key YOUR_API_KEY"
),
}
],
"restartRequired": True,
}
],
}
async def connect(self, url: str) -> 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).payload
class _ReadyGatewayClient(_FakeGatewayClient):
payload = {
"status": "ready",
"ready": True,
"summary": "Ready",
"counts": {"error": 0, "warn": 0, "info": 0, "ok": 2},
"findings": [
{
"id": "gateway.rpc.ready",
"severity": "ok",
"surface": "gateway",
"title": "Gateway RPC ready",
"detail": "The gateway accepted and handled doctor.status.",
"evidence": {},
"fixSteps": [],
"restartRequired": False,
},
{
"id": "provider.active.ready",
"severity": "ok",
"surface": "provider",
"title": "Active provider ready",
"detail": "openrouter is configured and buildable.",
"evidence": {},
"fixSteps": [],
"restartRequired": False,
},
],
}
class _OfflineGatewayClient(_FakeGatewayClient):
async def connect(self, url: str) -> None:
raise SystemExit(
"Cannot connect to OpenSquilla gateway.\n"
"Is the gateway running? Start it with: opensquilla gateway run\n"
"Error: connection refused"
)
def test_doctor_json_calls_doctor_status(monkeypatch) -> None:
_FakeGatewayClient.calls = []
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
result = runner.invoke(app, ["doctor", "--json"])
assert result.exit_code == 1
payload = json.loads(result.stdout)
assert payload["status"] == "action_required"
assert ("doctor.status", {"agentId": "main", "deep": True}) in _FakeGatewayClient.calls
def test_doctor_quick_skips_deep_memory_diagnostics(monkeypatch) -> None:
_FakeGatewayClient.calls = []
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
result = runner.invoke(app, ["doctor", "--quick", "--json"])
assert result.exit_code == 1
assert ("doctor.status", {"agentId": "main", "deep": False}) in _FakeGatewayClient.calls
def test_doctor_config_targets_gateway_from_config_path(tmp_path, monkeypatch) -> None:
_FakeGatewayClient.calls = []
target = tmp_path / "custom.toml"
target.write_text('host = "0.0.0.0"\nport = 20002\n', encoding="utf-8")
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
result = runner.invoke(app, ["doctor", "--json", "--config", str(target)])
assert result.exit_code == 1
assert ("connect", "ws://127.0.0.1:20002/ws") in _FakeGatewayClient.calls
assert ("doctor.status", {"agentId": "main", "deep": True}) in _FakeGatewayClient.calls
def test_doctor_config_derived_gateway_recovery_preserves_config_target(
tmp_path,
monkeypatch,
) -> None:
target = tmp_path / "custom.toml"
target.write_text('host = "0.0.0.0"\nport = 20002\n', encoding="utf-8")
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _OfflineGatewayClient)
result = runner.invoke(app, ["doctor", "--json", "--config", str(target)])
assert result.exit_code == 1
payload = json.loads(result.stdout)
finding = next(
finding
for finding in payload["findings"]
if finding["id"] == "gateway.unavailable"
)
commands = [step["command"] for step in finding["fixSteps"] if "command" in step]
assert commands[:2] == [
f"opensquilla gateway start --config {_config_arg(target)}",
f"opensquilla gateway status --json --config {_config_arg(target)}",
]
assert all("--bind 127.0.0.1" not in command for command in commands)
assert all("--port 20002" not in command for command in commands[:2])
assert finding["evidence"]["gatewayUrl"] == "ws://127.0.0.1:20002/ws"
def test_doctor_targets_existing_cwd_config_without_explicit_config(
tmp_path,
monkeypatch,
) -> None:
_FakeGatewayClient.calls = []
target = tmp_path / "opensquilla.toml"
target.write_text('host = "127.0.0.1"\nport = 20009\n', encoding="utf-8")
monkeypatch.chdir(tmp_path)
monkeypatch.delenv("OPENSQUILLA_GATEWAY_CONFIG_PATH", raising=False)
monkeypatch.delenv("OPENSQUILLA_GATEWAY_URL", raising=False)
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
result = runner.invoke(app, ["doctor", "--json"])
assert result.exit_code == 1
payload = json.loads(result.stdout)
assert ("connect", "ws://127.0.0.1:20009/ws") in _FakeGatewayClient.calls
assert payload["configPath"] == str(target)
provider_finding = next(
finding
for finding in payload["findings"]
if finding["id"] == "provider.active.not_configured"
)
commands = [step["command"] for step in provider_finding["fixSteps"] if "command" in step]
assert commands == [
"opensquilla providers configure openrouter --api-key YOUR_API_KEY "
f"--config {_config_arg(target)}"
]
def test_doctor_targets_env_config_without_explicit_config(
tmp_path,
monkeypatch,
) -> None:
_FakeGatewayClient.calls = []
target = tmp_path / "env-opensquilla.toml"
target.write_text('host = "127.0.0.1"\nport = 20010\n', encoding="utf-8")
monkeypatch.setenv("OPENSQUILLA_GATEWAY_CONFIG_PATH", str(target))
monkeypatch.delenv("OPENSQUILLA_GATEWAY_URL", raising=False)
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
result = runner.invoke(app, ["doctor", "--json"])
assert result.exit_code == 1
payload = json.loads(result.stdout)
assert ("connect", "ws://127.0.0.1:20010/ws") in _FakeGatewayClient.calls
assert payload["configPath"] == str(target)
provider_finding = next(
finding
for finding in payload["findings"]
if finding["id"] == "provider.active.not_configured"
)
commands = [step["command"] for step in provider_finding["fixSteps"] if "command" in step]
assert commands == [
"opensquilla providers configure openrouter --api-key YOUR_API_KEY "
f"--config {_config_arg(target)}"
]
def test_doctor_config_scopes_gateway_recovery_commands(tmp_path, monkeypatch) -> None:
_FakeGatewayClient.calls = []
target = tmp_path / "custom.toml"
target.write_text('host = "127.0.0.1"\nport = 20003\n', encoding="utf-8")
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
result = runner.invoke(app, ["doctor", "--json", "--config", str(target)])
assert result.exit_code == 1
payload = json.loads(result.stdout)
finding = next(
finding
for finding in payload["findings"]
if finding["id"] == "provider.active.not_configured"
)
commands = [step["command"] for step in finding["fixSteps"] if "command" in step]
assert commands == [
"opensquilla providers configure openrouter --api-key YOUR_API_KEY "
f"--config {_config_arg(target)}"
]
def test_doctor_config_scopes_config_set_recovery_commands(tmp_path, monkeypatch) -> None:
_FakeGatewayClient.calls = []
target = tmp_path / "custom.toml"
target.write_text('host = "127.0.0.1"\nport = 20003\n', encoding="utf-8")
original_payload = _FakeGatewayClient.payload
_FakeGatewayClient.payload = {
"status": "ready",
"ready": True,
"summary": "Ready, 1 optional setup item",
"counts": {"error": 0, "warn": 0, "info": 1, "ok": 1},
"impactCounts": {"blocks_ready": 0, "degrades": 0, "optional": 1, "none": 1},
"configPath": str(target),
"findings": [
{
"id": "logs.gateway_file_log.disabled",
"severity": "info",
"readinessImpact": "optional",
"surface": "logs",
"title": "Gateway file logging is disabled",
"detail": "Persistent gateway file logging is optional.",
"fixSteps": [
{
"label": "Enable gateway file logging",
"command": "opensquilla config set log_file_enabled true",
},
{
"label": "Restart gateway",
"command": "opensquilla gateway restart",
},
],
"restartRequired": True,
}
],
}
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
try:
result = runner.invoke(app, ["doctor", "--json", "--config", str(target)])
finally:
_FakeGatewayClient.payload = original_payload
assert result.exit_code == 0
payload = json.loads(result.stdout)
finding = next(
finding
for finding in payload["findings"]
if finding["id"] == "logs.gateway_file_log.disabled"
)
commands = [step["command"] for step in finding["fixSteps"] if "command" in step]
assert commands == [
f"opensquilla config set log_file_enabled true --config {_config_arg(target)}",
f"opensquilla gateway restart --config {_config_arg(target)}",
]
def test_doctor_bad_config_reports_config_recovery(tmp_path, monkeypatch) -> None:
_FakeGatewayClient.calls = []
target = tmp_path / "broken.toml"
target.write_text("[llm\n", encoding="utf-8")
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
result = runner.invoke(app, ["doctor", "--json", "--config", str(target)])
assert result.exit_code == 1
payload = json.loads(result.stdout)
ids = [finding["id"] for finding in payload["findings"]]
assert "config.local.unreadable" in ids
assert _FakeGatewayClient.calls == []
def test_doctor_bad_gateway_config_env_reports_config_recovery(
tmp_path, monkeypatch
) -> None:
_FakeGatewayClient.calls = []
target = tmp_path / "broken-env.toml"
target.write_text("[llm\n", encoding="utf-8")
monkeypatch.setenv("OPENSQUILLA_GATEWAY_CONFIG_PATH", str(target))
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
result = runner.invoke(app, ["doctor", "--json"])
assert result.exit_code == 1
payload = json.loads(result.stdout)
config_finding = next(
finding
for finding in payload["findings"]
if finding["id"] == "config.local.unreadable"
)
assert config_finding["evidence"]["configPath"] == str(target)
assert _FakeGatewayClient.calls == []
def test_doctor_explicit_gateway_context_ignores_env_config_path(
tmp_path, monkeypatch
) -> None:
_ReadyGatewayClient.calls = []
target = tmp_path / "env.toml"
target.write_text('host = "127.0.0.1"\nport = 20004\n', encoding="utf-8")
monkeypatch.setenv("OPENSQUILLA_GATEWAY_CONFIG_PATH", str(target))
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _ReadyGatewayClient)
result = runner.invoke(
app,
["doctor", "--json", "--gateway", "http://squilla.example.com:9443"],
)
assert result.exit_code == 0
payload = json.loads(result.stdout)
assert payload["gatewayUrl"] == "ws://squilla.example.com:9443/ws"
assert "configPath" not in payload
def test_doctor_remote_gateway_ignores_explicit_local_config_context(
tmp_path,
monkeypatch,
) -> None:
_FakeGatewayClient.calls = []
target = tmp_path / "local-only.toml"
target.write_text('host = "127.0.0.1"\nport = 20008\n', encoding="utf-8")
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
result = runner.invoke(
app,
[
"doctor",
"--json",
"--gateway",
"https://squilla.example.com",
"--config",
str(target),
],
)
assert result.exit_code == 1
payload = json.loads(result.stdout)
assert payload["gatewayUrl"] == "wss://squilla.example.com/ws"
assert "configPath" not in payload
assert "requestedConfigPath" not in payload
finding = next(
finding
for finding in payload["findings"]
if finding["id"] == "provider.active.not_configured"
)
commands = [step["command"] for step in finding["fixSteps"] if "command" in step]
assert commands == ["opensquilla providers configure openrouter --api-key YOUR_API_KEY"]
def test_doctor_ready_json_exits_zero(monkeypatch) -> None:
_ReadyGatewayClient.calls = []
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _ReadyGatewayClient)
result = runner.invoke(app, ["doctor", "--json"])
assert result.exit_code == 0
assert json.loads(result.stdout)["ready"] is True
def test_doctor_ready_table_summarizes_without_dumping_ok_findings(monkeypatch) -> None:
_ReadyGatewayClient.calls = []
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _ReadyGatewayClient)
result = runner.invoke(app, ["doctor"])
assert result.exit_code == 0
assert "No action needed" in result.stdout
assert "Gateway RPC ready" not in result.stdout
assert "Active provider ready" not in result.stdout
assert "Reference" not in result.stdout
def test_doctor_table_shows_gateway_config_and_agent_context(tmp_path, monkeypatch) -> None:
_ReadyGatewayClient.calls = []
target = tmp_path / "custom.toml"
target.write_text('host = "127.0.0.1"\nport = 20003\n', encoding="utf-8")
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _ReadyGatewayClient)
result = runner.invoke(
app,
["doctor", "--agent", "worker", "--config", str(target)],
)
assert result.exit_code == 0
assert "Gateway: ws://127.0.0.1:20003/ws" in result.stdout
assert f"Config: {target}" in result.stdout
assert "Agent: worker" in result.stdout
def test_doctor_config_reports_running_gateway_config_mismatch(
tmp_path,
monkeypatch,
) -> None:
_ReadyGatewayClient.calls = []
requested = tmp_path / "requested.toml"
running = tmp_path / "running.toml"
requested.write_text('host = "127.0.0.1"\nport = 20005\n', encoding="utf-8")
original_payload = _ReadyGatewayClient.payload
_ReadyGatewayClient.payload = {
**original_payload,
"configPath": str(running),
}
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _ReadyGatewayClient)
try:
result = runner.invoke(app, ["doctor", "--json", "--config", str(requested)])
finally:
_ReadyGatewayClient.payload = original_payload
assert result.exit_code == 1
payload = json.loads(result.stdout)
assert payload["ready"] is False
assert payload["status"] == "action_required"
assert payload["requestedConfigPath"] == str(requested)
assert payload["configPath"] == str(running)
finding = next(
finding
for finding in payload["findings"]
if finding["id"] == "gateway.config.mismatch"
)
assert finding["readinessImpact"] == "blocks_ready"
assert finding["evidence"]["requestedConfigPath"] == str(requested)
assert finding["evidence"]["runningConfigPath"] == str(running)
commands = [step["command"] for step in finding["fixSteps"] if "command" in step]
assert f"opensquilla gateway restart --config {_config_arg(requested)}" in commands
assert (
f"opensquilla gateway status --json --config {_config_arg(requested)}"
in commands
)
def test_doctor_config_mismatch_prioritizes_requested_gateway_recovery(
tmp_path,
monkeypatch,
) -> None:
_FakeGatewayClient.calls = []
requested = tmp_path / "requested.toml"
running = tmp_path / "running.toml"
requested.write_text('host = "127.0.0.1"\nport = 20006\n', encoding="utf-8")
running.write_text('host = "127.0.0.1"\nport = 20007\n', encoding="utf-8")
original_payload = _FakeGatewayClient.payload
_FakeGatewayClient.payload = {
**original_payload,
"configPath": str(running),
}
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
try:
result = runner.invoke(app, ["doctor", "--json", "--config", str(requested)])
finally:
_FakeGatewayClient.payload = original_payload
assert result.exit_code == 1
payload = json.loads(result.stdout)
assert [finding["id"] for finding in payload["findings"][:2]] == [
"gateway.config.mismatch",
"provider.active.not_configured",
]
def test_doctor_config_mismatch_keeps_running_findings_scoped_to_running_config(
tmp_path,
monkeypatch,
) -> None:
_FakeGatewayClient.calls = []
requested = tmp_path / "requested.toml"
running = tmp_path / "running.toml"
requested.write_text('host = "127.0.0.1"\nport = 20006\n', encoding="utf-8")
running.write_text('host = "127.0.0.1"\nport = 20007\n', encoding="utf-8")
original_payload = _FakeGatewayClient.payload
_FakeGatewayClient.payload = {
**original_payload,
"configPath": str(running),
}
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
try:
result = runner.invoke(app, ["doctor", "--json", "--config", str(requested)])
finally:
_FakeGatewayClient.payload = original_payload
assert result.exit_code == 1
payload = json.loads(result.stdout)
provider_finding = next(
finding
for finding in payload["findings"]
if finding["id"] == "provider.active.not_configured"
)
provider_commands = [
step["command"] for step in provider_finding["fixSteps"] if "command" in step
]
assert provider_commands == [
"opensquilla providers configure openrouter --api-key YOUR_API_KEY "
f"--config {_config_arg(running)}"
]
mismatch_finding = next(
finding
for finding in payload["findings"]
if finding["id"] == "gateway.config.mismatch"
)
mismatch_commands = [
step["command"] for step in mismatch_finding["fixSteps"] if "command" in step
]
assert f"opensquilla gateway restart --config {_config_arg(requested)}" in mismatch_commands
def test_doctor_table_prints_fix_steps(monkeypatch) -> None:
_FakeGatewayClient.calls = []
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
result = runner.invoke(app, ["doctor"])
assert result.exit_code == 1
assert "Active provider is not configured" in result.stdout
assert "opensquilla providers configure openrouter" in result.stdout
def test_doctor_degraded_table_exits_zero(monkeypatch) -> None:
_FakeGatewayClient.calls = []
original_payload = _FakeGatewayClient.payload
_FakeGatewayClient.payload = {
"status": "degraded",
"ready": True,
"summary": "Ready, 1 degraded check",
"counts": {"error": 0, "warn": 1, "info": 0, "ok": 1},
"findings": [
{
"id": "search.provider.not_configured",
"severity": "warn",
"surface": "search",
"title": "Search provider is not configured",
"detail": "brave is selected for web search but is missing required configuration.",
"fixSteps": [
{
"label": "Configure search",
"command": (
"opensquilla configure search "
"--search-provider brave --api-key YOUR_API_KEY"
),
}
],
"restartRequired": True,
}
],
}
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
try:
result = runner.invoke(app, ["doctor"])
finally:
_FakeGatewayClient.payload = original_payload
assert result.exit_code == 0
assert "Ready, 1 degraded check" in result.stdout
assert "Search provider is not configured" in result.stdout
assert "Recovery requires restart." in result.stdout
assert "Restart required." not in result.stdout
def test_doctor_table_explains_non_blocking_error_impact(monkeypatch) -> None:
_FakeGatewayClient.calls = []
original_payload = _FakeGatewayClient.payload
_FakeGatewayClient.payload = {
"status": "degraded",
"ready": True,
"summary": "Ready, 1 degraded check",
"counts": {"error": 1, "warn": 0, "info": 0, "ok": 1},
"impactCounts": {"blocks_ready": 0, "degrades": 1, "optional": 0, "none": 1},
"findings": [
{
"id": "channel.feishu.dead",
"severity": "error",
"readinessImpact": "degrades",
"surface": "channels",
"title": "Channel feishu is dead",
"detail": "The configured channel is not able to receive or send messages.",
"fixSteps": [
{
"label": "Restart channel",
"command": "opensquilla channels restart feishu --yes",
}
],
"restartRequired": False,
}
],
}
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
try:
result = runner.invoke(app, ["doctor"])
finally:
_FakeGatewayClient.payload = original_payload
assert result.exit_code == 0
assert "Channel feishu is dead" in result.stdout
assert "degrades" in result.stdout
def test_doctor_table_uses_readiness_impact_to_select_action_findings(monkeypatch) -> None:
_FakeGatewayClient.calls = []
original_payload = _FakeGatewayClient.payload
_FakeGatewayClient.payload = {
"status": "action_required",
"ready": False,
"summary": "1 action required",
"counts": {"error": 0, "warn": 0, "info": 2, "ok": 0},
"impactCounts": {"blocks_ready": 1, "degrades": 0, "optional": 1, "none": 0},
"findings": [
{
"id": "image_generation.disabled",
"severity": "info",
"readinessImpact": "optional",
"surface": "image_generation",
"title": "Image generation is disabled",
"detail": "Image generation is optional and is currently disabled.",
"fixSteps": [
{
"label": "Enable image generation",
"command": (
"opensquilla configure image-generation "
"--image-provider openai --api-key YOUR_API_KEY"
),
}
],
},
{
"id": "provider.active.not_configured",
"severity": "info",
"readinessImpact": "blocks_ready",
"surface": "provider",
"title": "Active provider is not configured",
"detail": "openrouter is active but missing required configuration.",
"fixSteps": [
{
"label": "Configure provider",
"command": (
"opensquilla providers configure openrouter --api-key YOUR_API_KEY"
),
}
],
"restartRequired": True,
},
],
}
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
try:
result = runner.invoke(app, ["doctor"])
finally:
_FakeGatewayClient.payload = original_payload
assert result.exit_code == 1
assert "Active provider is not configured" in result.stdout
assert "Image generation is disabled" not in result.stdout
assert "blocks readiness" in result.stdout
assert "Recovery" in result.stdout
assert "Optional setup" not in result.stdout
def test_doctor_table_uses_readiness_impact_to_label_optional_steps(monkeypatch) -> None:
_FakeGatewayClient.calls = []
original_payload = _FakeGatewayClient.payload
_FakeGatewayClient.payload = {
"status": "ready",
"ready": True,
"summary": "Ready, 1 optional setup item",
"counts": {"error": 0, "warn": 1, "info": 0, "ok": 1},
"impactCounts": {"blocks_ready": 0, "degrades": 0, "optional": 1, "none": 1},
"findings": [
{
"id": "sandbox.posture.soft",
"severity": "warn",
"readinessImpact": "optional",
"surface": "sandbox",
"title": "Sandbox posture can be hardened",
"detail": "Sandbox hardening is optional for this local setup.",
"fixSteps": [
{
"label": "Inspect sandbox",
"command": "opensquilla sandbox status --json",
}
],
}
],
}
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
try:
result = runner.invoke(app, ["doctor"])
finally:
_FakeGatewayClient.payload = original_payload
assert result.exit_code == 0
assert "Sandbox posture can be hardened" in result.stdout
assert "Optional setup" in result.stdout
assert "Recovery" not in result.stdout
def test_doctor_table_hides_non_actionable_findings_when_attention_is_needed(monkeypatch) -> None:
_FakeGatewayClient.calls = []
original_payload = _FakeGatewayClient.payload
_FakeGatewayClient.payload = {
**original_payload,
"findings": [
*original_payload["findings"],
{
"id": "image_generation.disabled",
"severity": "info",
"surface": "image_generation",
"title": "Image generation is disabled",
"detail": "Image generation is optional and is currently disabled.",
"evidence": {},
"fixSteps": [
{
"label": "Enable image generation",
"command": (
"opensquilla configure image-generation "
"--image-provider openai --api-key YOUR_API_KEY"
),
}
],
"restartRequired": False,
},
{
"id": "gateway.rpc.ready",
"severity": "ok",
"surface": "gateway",
"title": "Gateway RPC ready",
"detail": "The gateway accepted and handled doctor.status.",
"evidence": {},
"fixSteps": [],
"restartRequired": False,
},
],
}
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
try:
result = runner.invoke(app, ["doctor"])
finally:
_FakeGatewayClient.payload = original_payload
assert result.exit_code == 1
assert "Active provider is not configured" in result.stdout
assert "Image generation is disabled" not in result.stdout
assert "Gateway RPC ready" not in result.stdout
def test_doctor_table_labels_info_only_steps_as_optional(monkeypatch) -> None:
_FakeGatewayClient.calls = []
original_payload = _FakeGatewayClient.payload
_FakeGatewayClient.payload = {
"status": "ready",
"ready": True,
"summary": "Ready, 1 optional setup item",
"counts": {"error": 0, "warn": 0, "info": 1, "ok": 1},
"findings": [
{
"id": "image_generation.disabled",
"severity": "info",
"surface": "image_generation",
"title": "Image generation is disabled",
"detail": "Image generation is optional and is currently disabled.",
"evidence": {},
"fixSteps": [
{
"label": "Enable image generation",
"command": (
"opensquilla configure image-generation "
"--image-provider openai --api-key YOUR_API_KEY"
),
}
],
"restartRequired": False,
},
{
"id": "gateway.rpc.ready",
"severity": "ok",
"surface": "gateway",
"title": "Gateway RPC ready",
"detail": "The gateway accepted and handled doctor.status.",
"evidence": {},
"fixSteps": [],
"restartRequired": False,
},
],
}
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
try:
result = runner.invoke(app, ["doctor"])
finally:
_FakeGatewayClient.payload = original_payload
assert result.exit_code == 0
assert "Optional setup" in result.stdout
assert "Recovery" not in result.stdout
assert "Image generation is disabled" in result.stdout
assert "Gateway RPC ready" not in result.stdout
def test_doctor_reports_gateway_unavailable_as_json(monkeypatch) -> None:
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _OfflineGatewayClient)
monkeypatch.setattr("opensquilla.cli.doctor_cmd._local_config_findings", lambda: [])
result = runner.invoke(app, ["doctor", "--json"])
assert result.exit_code == 1
payload = json.loads(result.stdout)
assert payload["status"] == "unavailable"
assert payload["impactCounts"] == {
"blocks_ready": 1,
"degrades": 0,
"optional": 0,
"none": 0,
}
finding = payload["findings"][0]
assert finding["id"] == "gateway.unavailable"
assert finding["readinessImpact"] == "blocks_ready"
assert "opensquilla gateway start" in finding["fixSteps"][0]["command"]
assert "opensquilla gateway run" not in finding["detail"]
assert "recovery steps" in finding["detail"]
assert "opensquilla gateway run" in finding["evidence"]["error"]
def test_doctor_gateway_unavailable_uses_requested_gateway_url(monkeypatch) -> None:
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _OfflineGatewayClient)
monkeypatch.setattr("opensquilla.cli.doctor_cmd._local_config_findings", lambda: [])
result = runner.invoke(
app,
["doctor", "--json", "--gateway", "http://127.0.0.1:19999"],
)
assert result.exit_code == 1
payload = json.loads(result.stdout)
finding = payload["findings"][0]
assert finding["evidence"]["gatewayUrl"] == "ws://127.0.0.1:19999/ws"
commands = [step["command"] for step in finding["fixSteps"] if "command" in step]
assert commands[:2] == [
"opensquilla gateway start --bind 127.0.0.1 --port 19999",
"opensquilla gateway status --bind 127.0.0.1 --port 19999 --json",
]
def test_doctor_gateway_unavailable_uses_requested_config_path(
tmp_path,
monkeypatch,
) -> None:
from opensquilla.gateway.config import GatewayConfig
from opensquilla.onboarding.config_store import persist_config
target = tmp_path / "custom-config.toml"
persist_config(
GatewayConfig(
llm={
"provider": "openrouter",
"model": "deepseek/deepseek-v4-flash",
"api_key": "",
"api_key_env": "CUSTOM_LLM_KEY",
}
),
path=target,
)
monkeypatch.delenv("CUSTOM_LLM_KEY", raising=False)
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _OfflineGatewayClient)
result = runner.invoke(
app,
[
"doctor",
"--json",
"--gateway",
"http://127.0.0.1:19999",
"--config",
str(target),
],
)
assert result.exit_code == 1
payload = json.loads(result.stdout)
gateway_finding = next(
finding
for finding in payload["findings"]
if finding["id"] == "gateway.unavailable"
)
gateway_commands = [
step["command"] for step in gateway_finding["fixSteps"] if "command" in step
]
assert gateway_commands[:2] == [
"opensquilla gateway start --bind 127.0.0.1 --port 19999 "
f"--config {_config_arg(target)}",
(
"opensquilla gateway status --bind 127.0.0.1 --port 19999 "
f"--json --config {_config_arg(target)}"
),
]
assert f"opensquilla diagnostics status --config {_config_arg(target)}" in gateway_commands
finding = next(
finding
for finding in payload["findings"]
if finding["id"] == "config.local.needs_onboarding"
)
assert finding["evidence"]["configPath"] == str(target)
assert "CUSTOM_LLM_KEY" in finding["detail"]
commands = [step["command"] for step in finding["fixSteps"] if "command" in step]
assert commands[-2:] == [
f"opensquilla onboard status --json --config {_config_arg(target)}",
f"opensquilla onboard --if-needed --config {_config_arg(target)}",
]
def test_doctor_gateway_unavailable_prioritizes_unreadable_config(
tmp_path,
monkeypatch,
) -> None:
target = tmp_path / "broken.toml"
target.write_text("[llm\n", encoding="utf-8")
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _OfflineGatewayClient)
result = runner.invoke(
app,
[
"doctor",
"--json",
"--gateway",
"http://127.0.0.1:19999",
"--config",
str(target),
],
)
assert result.exit_code == 1
payload = json.loads(result.stdout)
ids = [finding["id"] for finding in payload["findings"]]
assert ids[:2] == ["config.local.unreadable", "gateway.unavailable"]
first_commands = [
step["command"] for step in payload["findings"][0]["fixSteps"] if "command" in step
]
assert first_commands == [
f"opensquilla onboard status --json --config {_config_arg(target)}",
f"opensquilla onboard --if-needed --config {_config_arg(target)}",
]
def test_doctor_gateway_unavailable_does_not_start_remote_gateway(monkeypatch) -> None:
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _OfflineGatewayClient)
monkeypatch.setattr("opensquilla.cli.doctor_cmd._local_config_findings", lambda: [])
result = runner.invoke(
app,
["doctor", "--json", "--gateway", "https://squilla.example.com"],
)
assert result.exit_code == 1
payload = json.loads(result.stdout)
finding = payload["findings"][0]
assert finding["evidence"]["gatewayUrl"] == "wss://squilla.example.com/ws"
commands = [step["command"] for step in finding["fixSteps"] if "command" in step]
assert all("gateway start" not in command for command in commands)
assert commands[0] == (
"opensquilla gateway status --gateway wss://squilla.example.com/ws --json"
)
details = [step.get("detail", "") for step in finding["fixSteps"]]
assert any("remote" in detail.lower() for detail in details)
def test_doctor_offline_prioritizes_local_config_before_gateway_restart(monkeypatch) -> None:
from opensquilla.cli import doctor_cmd
from opensquilla.health.model import HealthFinding
monkeypatch.setattr(
doctor_cmd,
"_local_config_findings",
lambda: [
HealthFinding(
id="config.local.needs_onboarding",
severity="error",
readiness_impact="blocks_ready",
surface="config",
title="Local configuration needs onboarding",
detail="Local config has incomplete sections.",
)
],
)
report = doctor_cmd._offline_report(
ConnectionError("connection refused"),
gateway_url="ws://127.0.0.1:19999/ws",
)
assert report["status"] == "unavailable"
assert report["ready"] is False
assert [finding["id"] for finding in report["findings"]] == [
"config.local.needs_onboarding",
"gateway.unavailable",
]
assert report["impactCounts"]["blocks_ready"] == 2
def test_doctor_offline_does_not_mix_local_config_into_remote_gateway(monkeypatch) -> None:
from opensquilla.cli import doctor_cmd
from opensquilla.health.model import HealthFinding
monkeypatch.setattr(
doctor_cmd,
"_local_config_findings",
lambda: [
HealthFinding(
id="config.local.needs_onboarding",
severity="error",
surface="config",
title="Local configuration needs onboarding",
detail="Local config has incomplete sections.",
)
],
)
report = doctor_cmd._offline_report(
ConnectionError("connection refused"),
gateway_url="wss://squilla.example.com/ws",
)
assert [finding["id"] for finding in report["findings"]] == ["gateway.unavailable"]
def test_local_config_findings_explain_missing_llm_env(monkeypatch) -> None:
from opensquilla.cli import doctor_cmd
from opensquilla.gateway.config import GatewayConfig
monkeypatch.delenv("CUSTOM_LLM_KEY", raising=False)
cfg = GatewayConfig(
llm={
"provider": "openrouter",
"model": "deepseek/deepseek-v4-flash",
"api_key": "",
"api_key_env": "CUSTOM_LLM_KEY",
}
)
findings = doctor_cmd._local_onboarding_findings(cfg)
assert len(findings) == 1
finding = findings[0]
assert finding.id == "config.local.needs_onboarding"
assert finding.severity == "error"
assert finding.readiness_impact == "blocks_ready"
assert finding.evidence["sections"]["llm"] == "degraded"
assert "CUSTOM_LLM_KEY" in finding.detail
assert any(
step.detail and "CUSTOM_LLM_KEY" in step.detail
for step in finding.fix_steps
)
assert [step.command for step in finding.fix_steps if step.command][-2:] == [
"opensquilla onboard status --json",
"opensquilla onboard --if-needed",
]
def test_local_config_findings_explain_optional_env_references(monkeypatch) -> None:
from opensquilla.cli import doctor_cmd
from opensquilla.gateway.config import GatewayConfig
monkeypatch.delenv("CUSTOM_SEARCH_KEY", raising=False)
monkeypatch.delenv("CUSTOM_IMAGE_KEY", raising=False)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
cfg = GatewayConfig(
llm={
"provider": "openrouter",
"model": "deepseek/deepseek-v4-flash",
"api_key": "test-key",
},
search_provider="brave",
search_api_key="",
search_api_key_env="CUSTOM_SEARCH_KEY",
image_generation={
"enabled": True,
"primary": "openai/dall-e-3",
"providers": {
"openai": {
"api_key": "",
"api_key_env": "CUSTOM_IMAGE_KEY",
}
},
},
)
findings = doctor_cmd._local_onboarding_findings(cfg)
assert len(findings) == 1
finding = findings[0]
assert finding.severity == "warn"
assert finding.readiness_impact == "degrades"
assert finding.evidence["sections"]["search"] == "degraded"
assert finding.evidence["sections"]["image_generation"] == "degraded"
assert "CUSTOM_SEARCH_KEY" in finding.detail
assert "CUSTOM_IMAGE_KEY" in finding.detail
detail_steps = " ".join(step.detail or "" for step in finding.fix_steps)
assert "CUSTOM_SEARCH_KEY" in detail_steps
assert "CUSTOM_IMAGE_KEY" in detail_steps