mirror of
https://github.com/TencentCloud/Octop.git
synced 2026-10-20 06:19:17 +08:00
`octop models active` prints " / " when no global default model is set: the provider/model separator with nothing on either side and no hint. `octop agent list` leaves the model cell empty for every agent without its own model, which reads as "this agent has no model" and hides that an agent with its own model overrides the global default. When no default is set, `models active` now says so and shows the command that sets one. `agent list` fills empty model cells with "(global: provider/model)", or "(not set)" when there is no global default either; agents with their own model are unchanged. Both strings come from the zh/en bundles. JSON output (`--json`) is unchanged. Refs #1930, #1931
140 lines
4.5 KiB
Python
140 lines
4.5 KiB
Python
"""Tests for offline CLI DB helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from octop.cli.main import cli
|
|
from octop.infra.db.pool import SqlitePool
|
|
from octop.infra.db.repos.agents import AgentRepo
|
|
from octop.infra.db.repos.threads import ThreadRepo
|
|
from octop.infra.utils.paths import PathLayout
|
|
from octop.infra.utils.ulid import new_ulid
|
|
|
|
|
|
@pytest.fixture
|
|
def fake_home(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
|
monkeypatch.setenv("HOME", str(tmp_path))
|
|
monkeypatch.setattr(Path, "home", classmethod(lambda cls: tmp_path))
|
|
return tmp_path
|
|
|
|
|
|
def _bootstrap(fake_home: Path) -> None:
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
cli,
|
|
["init", "--admin-username", "alice", "--admin-password", "TestPass12", "--yes"],
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
|
|
|
|
def test_list_agents_offline(fake_home: Path) -> None:
|
|
_bootstrap(fake_home)
|
|
paths = PathLayout(fake_home / ".octop")
|
|
db = SqlitePool(paths.db)
|
|
AgentRepo(db).create(agent_id="ag1", user_id=1, name="Bot")
|
|
db.close()
|
|
|
|
from octop.cli.support.db import list_agents_offline
|
|
|
|
rows = list_agents_offline(as_user="alice")
|
|
assert len(rows) == 1
|
|
assert rows[0]["agent_id"] == "ag1"
|
|
|
|
|
|
def test_list_threads_offline(fake_home: Path) -> None:
|
|
_bootstrap(fake_home)
|
|
paths = PathLayout(fake_home / ".octop")
|
|
db = SqlitePool(paths.db)
|
|
AgentRepo(db).create(agent_id="ag1", user_id=1, name="Bot")
|
|
tid = new_ulid()
|
|
ThreadRepo(db).insert(
|
|
thread_id=tid,
|
|
agent_id="ag1",
|
|
user_id=1,
|
|
channel_type="dashboard",
|
|
session_key="ag1:dashboard:1:dm",
|
|
title="hello",
|
|
)
|
|
db.close()
|
|
|
|
from octop.cli.support.db import list_threads_offline
|
|
|
|
rows = list_threads_offline(agent_id="ag1", as_user="alice", limit=10)
|
|
assert len(rows) == 1
|
|
assert rows[0]["thread_id"] == tid
|
|
assert rows[0]["has_messages"] is None
|
|
|
|
|
|
def test_agent_list_offline_flag(fake_home: Path) -> None:
|
|
_bootstrap(fake_home)
|
|
paths = PathLayout(fake_home / ".octop")
|
|
db = SqlitePool(paths.db)
|
|
AgentRepo(db).create(agent_id="ag1", user_id=1, name="Bot")
|
|
db.close()
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["agent", "list", "--user", "alice"])
|
|
assert result.exit_code == 0, result.output
|
|
assert "ag1" in result.output
|
|
|
|
|
|
@pytest.fixture
|
|
def english_cli(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr("octop.cli.support.db.resolve_cli_locale", lambda: "en")
|
|
monkeypatch.setenv("COLUMNS", "200")
|
|
|
|
|
|
@pytest.mark.usefixtures("english_cli")
|
|
def test_agent_list_shows_which_agents_run_on_the_global_model(fake_home: Path) -> None:
|
|
_bootstrap(fake_home)
|
|
paths = PathLayout(fake_home / ".octop")
|
|
db = SqlitePool(paths.db)
|
|
AgentRepo(db).create(agent_id="ag1", user_id=1, name="Bot")
|
|
AgentRepo(db).create(agent_id="ag2", user_id=1, name="Coder", default_model="acme/big")
|
|
db.close()
|
|
runner = CliRunner()
|
|
assert (
|
|
runner.invoke(cli, ["models", "active", "--provider", "acme", "--model", "small"]).exit_code
|
|
== 0
|
|
)
|
|
|
|
result = runner.invoke(cli, ["agent", "list", "--user", "alice"])
|
|
assert result.exit_code == 0, result.output
|
|
rows = {line.split("│")[1].strip(): line for line in result.output.splitlines() if "│" in line}
|
|
assert "(global: acme/small)" in rows["ag1"]
|
|
assert "acme/big" in rows["ag2"]
|
|
assert "global" not in rows["ag2"]
|
|
|
|
|
|
@pytest.mark.usefixtures("english_cli")
|
|
def test_agent_list_says_when_no_model_is_set_anywhere(fake_home: Path) -> None:
|
|
_bootstrap(fake_home)
|
|
paths = PathLayout(fake_home / ".octop")
|
|
db = SqlitePool(paths.db)
|
|
AgentRepo(db).create(agent_id="ag1", user_id=1, name="Bot")
|
|
db.close()
|
|
|
|
result = CliRunner().invoke(cli, ["agent", "list", "--user", "alice"])
|
|
assert result.exit_code == 0, result.output
|
|
assert "(not set)" in result.output
|
|
|
|
|
|
@pytest.mark.usefixtures("english_cli")
|
|
def test_models_active_explains_an_unset_default(fake_home: Path) -> None:
|
|
_bootstrap(fake_home)
|
|
runner = CliRunner()
|
|
|
|
unset = runner.invoke(cli, ["models", "active"])
|
|
assert unset.exit_code == 0, unset.output
|
|
assert unset.output.strip() != "/"
|
|
assert "No global default model is set" in unset.output
|
|
assert "octop models active --provider" in unset.output
|
|
|
|
runner.invoke(cli, ["models", "active", "--provider", "acme", "--model", "small"])
|
|
shown = runner.invoke(cli, ["models", "active"])
|
|
assert shown.output.strip() == "acme / small"
|