Files
Octop/tests/unit/cli/test_chats_cmd.py
jubaoliangandCursor ae7dfc8b0f feat(chat): add Ask / Plan / Craft conversation modes (#934)
* feat(chat): add Ask / Plan / Craft conversation modes

Keep mode sticky per thread, enforce Ask/Plan in harness-agent, and let Plan write a workspace plan before Craft executes it.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Update pyproject.toml
2026-09-21 22:18:28 +08:00

109 lines
3.6 KiB
Python

"""Tests for the `octop chats` group."""
from __future__ import annotations
from pathlib import Path
import click
import pytest
from click.testing import CliRunner
from octop.cli.main import cli
def test_chats_group_help_lists_all_subcommands() -> None:
runner = CliRunner()
result = runner.invoke(cli, ["chats", "--help"])
assert result.exit_code == 0
for sub in ("list", "get", "create", "update", "delete", "send", "repl"):
assert sub in result.output
def test_chats_send_help_lists_mode() -> None:
runner = CliRunner()
result = runner.invoke(cli, ["chats", "send", "--help"])
assert result.exit_code == 0
assert "--mode" in result.output
def test_chats_list_requires_agent(monkeypatch: pytest.MonkeyPatch) -> None:
import octop.cli.support.ctx as ctx_mod
def _missing(_agent: str | None) -> str:
click.echo("error: --agent is required", err=True)
raise SystemExit(2)
monkeypatch.setattr(ctx_mod, "require_agent", _missing)
runner = CliRunner()
result = runner.invoke(cli, ["chats", "list"])
assert result.exit_code == 2
def test_chats_list_uses_offline_db(monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict = {}
def fake_list_threads_offline(**kwargs):
captured.update(kwargs)
return [{"thread_id": "t1", "title": "hi", "is_active": True}]
from octop.cli.support import db as db_mod
monkeypatch.setattr(db_mod, "list_threads_offline", fake_list_threads_offline)
runner = CliRunner()
result = runner.invoke(cli, ["--json", "chats", "list", "--agent", "agent-1"])
assert result.exit_code == 0, result.output
assert captured["agent_id"] == "agent-1"
def test_chats_update_sends_title(monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict = {}
def fake_update_thread_offline(agent_id, thread_id, *, title=None, pinned=None):
captured["agent_id"] = agent_id
captured["thread_id"] = thread_id
captured["title"] = title
return {"thread_id": thread_id, "title": title}
from octop.cli.support import offline_ops as offline_mod
monkeypatch.setattr(offline_mod, "resolve_cron_user_id", lambda *_a, **_k: 1)
monkeypatch.setattr(offline_mod, "update_thread_offline", fake_update_thread_offline)
runner = CliRunner()
result = runner.invoke(
cli, ["chats", "update", "abc", "--agent", "agent-1", "--title", "renamed"]
)
assert result.exit_code == 0
assert captured["title"] == "renamed"
def test_delete_thread_offline_unbinds_session(tmp_octop_home: Path) -> None:
from octop.cli.support.db import open_cli_services
from octop.cli.support.offline_ops import (
create_thread_offline,
delete_thread_offline,
)
from octop.infra.db.repos.agents import AgentRepo
from octop.infra.db.repos.users import UserRepo
assert (
CliRunner()
.invoke(
cli,
["init", "--admin-username", "alice", "--admin-password", "TestPass12", "--yes"],
)
.exit_code
== 0
)
with open_cli_services(home=tmp_octop_home) as svc:
uid = UserRepo(svc.db).get_by_username("alice")
assert uid is not None
user_id = int(uid.id)
AgentRepo(svc.db).create(agent_id="ag1", user_id=user_id, name="main")
created = create_thread_offline(agent_id="ag1", user_id=user_id, home=tmp_octop_home)
delete_thread_offline("ag1", created["thread_id"], home=tmp_octop_home)
with open_cli_services(home=tmp_octop_home) as svc:
assert svc.thread_repo.get(created["thread_id"]) is None
assert svc.session_repo.get(created["session_key"]) is None