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

134 lines
4.6 KiB
Python

"""End-to-end coverage for the ``onboard --if-needed`` gate.
The gate represents first-run readiness. Optional capability sections still
surface action-required metadata, but they must not keep provider-complete
installs inside the interactive wizard forever.
Console output is captured by monkeypatching ``opensquilla.cli.onboard_cmd.console``
rather than ``capsys``. Rich consoles bind to a stdout reference at import
time, which makes ``capsys`` brittle under full-suite execution.
"""
from __future__ import annotations
import pytest
from typer.testing import CliRunner
from opensquilla.cli.main import app
from opensquilla.gateway.config import GatewayConfig, LlmProviderConfig
from opensquilla.onboarding.status import get_onboarding_status
class _RecordingConsole:
def __init__(self) -> None:
self.messages: list[str] = []
def print(self, message: str = "", *_a, **_kw) -> None:
self.messages.append(str(message))
def joined(self) -> str:
return "\n".join(self.messages)
@pytest.fixture()
def runner() -> CliRunner:
return CliRunner()
@pytest.fixture()
def recorder(monkeypatch) -> _RecordingConsole:
instance = _RecordingConsole()
monkeypatch.setattr("opensquilla.cli.onboard_cmd.console", instance)
return instance
def _llm_ok_cfg() -> GatewayConfig:
cfg = GatewayConfig()
cfg.llm = LlmProviderConfig(
provider="openrouter",
model="m",
api_key="sk-x",
base_url="https://openrouter.ai/api/v1",
)
return cfg
def test_if_needed_skips_when_all_sections_ok_or_optional(
monkeypatch, tmp_path, recorder, runner
):
cfg = _llm_ok_cfg()
# Fresh config persisted to disk so has_config=True.
config_path = tmp_path / "config.toml"
config_path.write_text("")
cfg.config_path = str(config_path)
monkeypatch.setattr("opensquilla.cli.onboard_cmd.load_config", lambda _path=None: cfg)
result = runner.invoke(app, ["onboard", "--if-needed"])
assert result.exit_code == 0, result.output
assert "core setup is ready" in recorder.joined()
assert "optional capabilities need action" in recorder.joined()
assert "Optional next moves" in recorder.joined()
assert "Channel recipes:" in recorder.joined()
assert "Image recipes:" in recorder.joined()
def test_if_needed_surfaces_optional_actions_without_running_wizard(
monkeypatch, tmp_path, recorder, runner
):
cfg = _llm_ok_cfg()
cfg.llm = LlmProviderConfig(
provider="deepseek",
model="m",
api_key="sk-x",
base_url="https://api.deepseek.com/v1",
)
cfg.search_provider = "brave"
cfg.search_api_key = ""
cfg.search_api_key_env = ""
cfg.image_generation.enabled = True
cfg.image_generation.primary = "openai/gpt-image-1"
cfg.memory.embedding.provider = "auto"
config_path = tmp_path / "config.toml"
config_path.write_text("")
cfg.config_path = str(config_path)
monkeypatch.delenv("BRAVE_API_KEY", raising=False)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
monkeypatch.setattr("opensquilla.cli.onboard_cmd.load_config", lambda _path=None: cfg)
status = get_onboarding_status(cfg)
assert status.needs_onboarding is False
action_sections = ("search", "image_generation")
for section in action_sections:
assert status.section_details[section]["actionRequired"] is True
# Guard the assertion: this path should exit before the wizard is reached.
monkeypatch.setattr("opensquilla.onboarding.flow._is_tty", lambda: False)
result = runner.invoke(app, ["onboard", "--if-needed"])
assert result.exit_code == 0, result.output
assert "core setup is ready" in recorder.joined()
assert "optional capabilities need action" in recorder.joined()
for section in action_sections:
assert status.section_details[section]["label"] in recorder.joined()
assert "already complete" not in recorder.joined()
assert "unfinished sections" not in recorder.joined()
def test_onboard_status_subcommand_emits_json(monkeypatch, tmp_path, runner):
cfg = _llm_ok_cfg()
config_path = tmp_path / "config.toml"
config_path.write_text("")
cfg.config_path = str(config_path)
monkeypatch.setattr("opensquilla.cli.onboard_cmd.load_config", lambda _path=None: cfg)
result = runner.invoke(app, ["onboard", "status", "--json"])
assert result.exit_code == 0, result.output
import json
payload = json.loads(result.output.strip().splitlines()[-1])
assert payload["needsOnboarding"] is False
assert payload["sections"]["llm"] == "ok"
assert "router" in payload["sections"]