"""Helper script for session_cli.robot smoke tests. Each subcommand is a self-contained check that prints a sentinel on success. """ from __future__ import annotations import os import sys import tempfile from datetime import datetime from pathlib import Path from unittest.mock import MagicMock, patch # Ensure local source tree is importable _SRC = str(Path(__file__).resolve().parents[1] / "src") if _SRC not in sys.path: sys.path.insert(0, _SRC) from typer.testing import CliRunner # noqa: E402 from ulid import ULID # noqa: E402 from cleveragents.application.services.session_workflow import TellResult # noqa: E402 from cleveragents.cli.commands import session as session_mod # noqa: E402 from cleveragents.cli.commands.session import app as session_app # noqa: E402 from cleveragents.domain.models.core.session import ( # noqa: E402 MessageRole, Session, SessionMessage, SessionNotFoundError, SessionTokenUsage, ) runner = CliRunner() def _mock_session( session_id: str | None = None, actor_name: str | None = None, messages: list[SessionMessage] | None = None, ) -> Session: """Create a test Session instance.""" return Session( session_id=session_id or str(ULID()), actor_name=actor_name, namespace="local", messages=messages or [], token_usage=SessionTokenUsage( input_tokens=100, output_tokens=50, estimated_cost=0.005, ), created_at=datetime.now(), updated_at=datetime.now(), ) def _mock_message( role: MessageRole = MessageRole.USER, content: str = "Hello", sequence: int = 0, ) -> SessionMessage: return SessionMessage( message_id=str(ULID()), role=role, content=content, sequence=sequence, timestamp=datetime.now(), ) def _setup_service() -> MagicMock: """Create and install a mock service.""" svc = MagicMock() session_mod._service = svc return svc def _teardown() -> None: session_mod._service = None # --------------------------------------------------------------------------- # Subcommands # --------------------------------------------------------------------------- def create_default() -> None: svc = _setup_service() svc.create.return_value = _mock_session() try: result = runner.invoke(session_app, ["create"]) assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}" print("session-cli-create-default-ok") finally: _teardown() def create_actor() -> None: svc = _setup_service() svc.create.return_value = _mock_session(actor_name="openai/gpt-4") try: result = runner.invoke(session_app, ["create", "--actor", "openai/gpt-4"]) assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}" assert "openai/gpt-4" in result.output print("session-cli-create-actor-ok") finally: _teardown() def list_empty() -> None: svc = _setup_service() svc.list.return_value = [] try: result = runner.invoke(session_app, ["list"]) assert result.exit_code == 0 assert "No sessions found" in result.output print("session-cli-list-empty-ok") finally: _teardown() def list_populated() -> None: svc = _setup_service() svc.list.return_value = [ _mock_session(actor_name="openai/gpt-4"), _mock_session(), ] try: result = runner.invoke(session_app, ["list"]) assert result.exit_code == 0 assert "Sessions" in result.output print("session-cli-list-populated-ok") finally: _teardown() def show_valid() -> None: sid = str(ULID()) svc = _setup_service() session = _mock_session( session_id=sid, messages=[ _mock_message(MessageRole.USER, "Hello", 0), _mock_message(MessageRole.ASSISTANT, "Hi there", 1), ], ) svc.get.return_value = session try: result = runner.invoke(session_app, ["show", sid]) assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}" assert "Session Summary" in result.output print("session-cli-show-valid-ok") finally: _teardown() def show_not_found() -> None: svc = _setup_service() svc.get.side_effect = SessionNotFoundError("not found") try: result = runner.invoke(session_app, ["show", "INVALID"]) assert result.exit_code != 0 assert "Session not found" in result.output print("session-cli-show-not-found-ok") finally: _teardown() def delete_yes() -> None: sid = str(ULID()) svc = _setup_service() svc.get.return_value = _mock_session(session_id=sid) svc.delete.return_value = None try: result = runner.invoke(session_app, ["delete", sid, "--yes"]) assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}" assert "deleted" in result.output print("session-cli-delete-yes-ok") finally: _teardown() def export_import_roundtrip() -> None: """Test export to file then import back, verifying Rich panels are rendered.""" sid = str(ULID()) svc = _setup_service() session = _mock_session(session_id=sid, actor_name="openai/gpt-4") svc.get.return_value = session export_data = session.as_export_dict() svc.export_session.return_value = export_data fd, path = tempfile.mkstemp(suffix=".json") os.close(fd) os.unlink(path) try: # Export — verify Rich panels are rendered result = runner.invoke(session_app, ["export", sid, "--output", path]) assert result.exit_code == 0, f"export exit={result.exit_code}: {result.output}" assert os.path.exists(path), "Export file was not created" assert "Session Export" in result.output, ( f"Missing 'Session Export' panel in output:\n{result.output}" ) assert "Contents" in result.output, ( f"Missing 'Contents' panel in output:\n{result.output}" ) assert "Integrity" in result.output, ( f"Missing 'Integrity' panel in output:\n{result.output}" ) assert "Export completed" in result.output, ( f"Missing 'Export completed' in output:\n{result.output}" ) # Set up import mock imported = _mock_session(actor_name="openai/gpt-4") svc.import_session.return_value = imported # Import result = runner.invoke(session_app, ["import", "--input", path]) assert result.exit_code == 0, f"import exit={result.exit_code}: {result.output}" assert "Session Import" in result.output assert "Validation" in result.output assert "Merge" in result.output assert "Import completed" in result.output print("session-cli-export-import-roundtrip-ok") finally: if os.path.exists(path): os.unlink(path) _teardown() def import_rich_panels() -> None: """Test that session import renders Session Import, Validation, and Merge panels.""" import json as _json svc = _setup_service() session = _mock_session(actor_name="openai/gpt-4") imported = _mock_session(actor_name="openai/gpt-4") svc.import_session.return_value = imported fd, path = tempfile.mkstemp(suffix=".json") with os.fdopen(fd, "w") as fh: _json.dump(session.as_export_dict(), fh, default=str) try: result = runner.invoke(session_app, ["import", "--input", path]) assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}" assert "Session Import" in result.output, ( f"Missing 'Session Import' panel: {result.output}" ) assert "Validation" in result.output, ( f"Missing 'Validation' panel: {result.output}" ) assert "Merge" in result.output, f"Missing 'Merge' panel: {result.output}" assert "Import completed" in result.output, ( f"Missing 'Import completed': {result.output}" ) assert "Input:" in result.output, f"Missing 'Input:' field: {result.output}" assert "Session ID:" in result.output, ( f"Missing 'Session ID:' field: {result.output}" ) assert "Checksum:" in result.output, ( f"Missing 'Checksum:' field: {result.output}" ) assert "Actor Ref:" in result.output, ( f"Missing 'Actor Ref:' field: {result.output}" ) assert "Existing:" in result.output, ( f"Missing 'Existing:' field: {result.output}" ) assert "Strategy:" in result.output, ( f"Missing 'Strategy:' field: {result.output}" ) print("session-cli-import-rich-panels-ok") finally: if os.path.exists(path): os.unlink(path) _teardown() def tell_message() -> None: sid = str(ULID()) svc = _setup_service() # Session must have actor_name so tell can proceed without --actor flag. svc.get.return_value = _mock_session(session_id=sid, actor_name="openai/gpt-4") svc.get_messages.return_value = [] # Build a mock workflow that returns a canned TellResult. mock_wf = MagicMock() mock_wf.tell.return_value = TellResult( session_id=sid, user_message="Hello", assistant_message="Acknowledged: Hello", input_tokens=5, output_tokens=5, cost=0.0, duration_ms=0.0, tool_calls_count=0, ) try: with patch( "cleveragents.cli.commands.session._build_session_workflow", return_value=mock_wf, ): result = runner.invoke(session_app, ["tell", "--session", sid, "Hello"]) assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}" assert "Acknowledged" in result.output print("session-cli-tell-message-ok") finally: _teardown() def export_rich_panels() -> None: """Test that export renders all three spec-required Rich panels.""" sid = str(ULID()) svc = _setup_service() session = _mock_session(session_id=sid, actor_name="openai/gpt-4") svc.get.return_value = session export_data = session.as_export_dict() svc.export_session.return_value = export_data fd, path = tempfile.mkstemp(suffix=".json") os.close(fd) os.unlink(path) try: result = runner.invoke(session_app, ["export", sid, "--output", path]) assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}" assert "Session Export" in result.output, ( f"Missing 'Session Export' panel:\n{result.output}" ) assert "Contents" in result.output, ( f"Missing 'Contents' panel:\n{result.output}" ) assert "Integrity" in result.output, ( f"Missing 'Integrity' panel:\n{result.output}" ) assert "Export completed" in result.output, ( f"Missing 'Export completed':\n{result.output}" ) assert sid in result.output, f"Session ID missing from output:\n{result.output}" print("session-cli-export-rich-panels-ok") finally: if os.path.exists(path): os.unlink(path) _teardown() def export_stdout_rich_panels() -> None: """Test that stdout export also renders Rich panels.""" sid = str(ULID()) svc = _setup_service() session = _mock_session(session_id=sid, actor_name="openai/gpt-4") svc.get.return_value = session export_data = session.as_export_dict() svc.export_session.return_value = export_data try: result = runner.invoke(session_app, ["export", sid]) assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}" assert "Session Export" in result.output, ( f"Missing 'Session Export' panel:\n{result.output}" ) assert "(stdout)" in result.output, ( f"Missing '(stdout)' indicator:\n{result.output}" ) assert "Export completed" in result.output, ( f"Missing 'Export completed':\n{result.output}" ) print("session-cli-export-stdout-rich-panels-ok") finally: _teardown() # --------------------------------------------------------------------------- # Dispatcher # --------------------------------------------------------------------------- _COMMANDS: dict[str, object] = { "create-default": create_default, "create-actor": create_actor, "list-empty": list_empty, "list-populated": list_populated, "show-valid": show_valid, "show-not-found": show_not_found, "delete-yes": delete_yes, "export-import-roundtrip": export_import_roundtrip, "import-rich-panels": import_rich_panels, "export-rich-panels": export_rich_panels, "export-stdout-rich-panels": export_stdout_rich_panels, "tell-message": tell_message, } if __name__ == "__main__": if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS: print(f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>", file=sys.stderr) sys.exit(1) cmd = _COMMANDS[sys.argv[1]] cmd() # type: ignore[operator]