143 lines
3.9 KiB
Python
143 lines
3.9 KiB
Python
"""Tests for agent command no-key three-section error output."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
import typer
|
|
from typer.testing import CliRunner
|
|
|
|
from opensquilla.cli.agent_cmd import AgentRunResult
|
|
|
|
# Provider env vars the project supports
|
|
_PROVIDER_ENV_VARS = [
|
|
"OPENROUTER_API_KEY",
|
|
"OPENAI_API_KEY",
|
|
"ANTHROPIC_API_KEY",
|
|
"DEEPSEEK_API_KEY",
|
|
"GEMINI_API_KEY",
|
|
"DASHSCOPE_API_KEY",
|
|
"MOONSHOT_API_KEY",
|
|
"MISTRAL_API_KEY",
|
|
"GROQ_API_KEY",
|
|
]
|
|
|
|
_NO_KEY_RESULT = AgentRunResult(
|
|
status="error",
|
|
agent_id="main",
|
|
session_key="agent:main:main",
|
|
text="",
|
|
usage={},
|
|
errors=[{"message": "No provider available", "code": "no_provider"}],
|
|
)
|
|
|
|
|
|
def _make_app() -> typer.Typer:
|
|
from opensquilla.cli.agent_cmd import run_agent_command
|
|
|
|
app = typer.Typer()
|
|
app.command()(run_agent_command)
|
|
return app
|
|
|
|
|
|
def _mock_patch():
|
|
return patch(
|
|
"opensquilla.cli.agent_cmd.run_agent_once",
|
|
new=AsyncMock(return_value=_NO_KEY_RESULT),
|
|
)
|
|
|
|
|
|
def test_tty_message_contains_onboard_and_envvar(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""Error output contains 'opensquilla onboard' and at least one provider env var."""
|
|
for key in _PROVIDER_ENV_VARS:
|
|
monkeypatch.delenv(key, raising=False)
|
|
monkeypatch.setenv("HOME", str(tmp_path))
|
|
monkeypatch.setenv("USERPROFILE", str(tmp_path))
|
|
|
|
runner = CliRunner()
|
|
app = _make_app()
|
|
|
|
with _mock_patch():
|
|
result = runner.invoke(app, ["-m", "hi"])
|
|
|
|
combined = result.output or ""
|
|
assert "opensquilla onboard" in combined
|
|
assert any(var in combined for var in _PROVIDER_ENV_VARS)
|
|
|
|
|
|
def test_no_color_env_strips_ansi_but_keeps_three_sections(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""With NO_COLOR=1 output has no ANSI codes but still shows all three section labels."""
|
|
for key in _PROVIDER_ENV_VARS:
|
|
monkeypatch.delenv(key, raising=False)
|
|
monkeypatch.setenv("NO_COLOR", "1")
|
|
monkeypatch.setenv("HOME", str(tmp_path))
|
|
monkeypatch.setenv("USERPROFILE", str(tmp_path))
|
|
|
|
runner = CliRunner()
|
|
app = _make_app()
|
|
|
|
with _mock_patch():
|
|
result = runner.invoke(app, ["-m", "hi"])
|
|
|
|
combined = result.output or ""
|
|
|
|
# No ANSI escape sequences when NO_COLOR is set
|
|
assert not re.search(r"\x1b\[", combined), "ANSI escape codes found with NO_COLOR=1"
|
|
|
|
# All three section labels must be present
|
|
assert "Symptom" in combined
|
|
assert "Cause" in combined
|
|
assert "Next" in combined
|
|
|
|
|
|
def test_term_dumb_does_not_raise(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""TERM=dumb environment: command completes without raising an unexpected exception."""
|
|
for key in _PROVIDER_ENV_VARS:
|
|
monkeypatch.delenv(key, raising=False)
|
|
monkeypatch.setenv("TERM", "dumb")
|
|
monkeypatch.setenv("HOME", str(tmp_path))
|
|
monkeypatch.setenv("USERPROFILE", str(tmp_path))
|
|
|
|
runner = CliRunner()
|
|
app = _make_app()
|
|
|
|
with _mock_patch():
|
|
result = runner.invoke(app, ["-m", "hi"])
|
|
|
|
# No unexpected exception: only SystemExit or clean exit allowed
|
|
assert result.exception is None or isinstance(result.exception, SystemExit)
|
|
|
|
combined = result.output or ""
|
|
assert len(combined) > 0, "Expected some output even under TERM=dumb"
|
|
|
|
|
|
def test_exit_code_nonzero_when_no_key(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""Exit code is non-zero when no provider key is available."""
|
|
for key in _PROVIDER_ENV_VARS:
|
|
monkeypatch.delenv(key, raising=False)
|
|
monkeypatch.setenv("HOME", str(tmp_path))
|
|
monkeypatch.setenv("USERPROFILE", str(tmp_path))
|
|
|
|
runner = CliRunner()
|
|
app = _make_app()
|
|
|
|
with _mock_patch():
|
|
result = runner.invoke(app, ["-m", "hi"])
|
|
|
|
assert result.exit_code != 0
|