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