feat: add daemon CLI rendering functions and tests

- Add print_daemon_table() function to display daemon list in rich table format
- Add print_daemon_detail() function to display individual daemon information
- Update render_output() to support daemon rendering with json/table formats
- Add test for daemon create and list CLI commands with output validation
- Integrate daemon output rendering into CLI workflow
This commit is contained in:
2026-05-14 06:38:21 +00:00
parent 3bb3ff547d
commit f4663f202e
2 changed files with 72 additions and 0 deletions
+47
View File
@@ -13,6 +13,7 @@ from rich.panel import Panel
from rich.table import Table from rich.table import Table
from app.core.errors import FeatureNotReadyError from app.core.errors import FeatureNotReadyError
from app.domain.models import Daemon
from app.schemas.auth import AuthTokenResponse from app.schemas.auth import AuthTokenResponse
from app.schemas.portfolio import CommentResponse, PortfolioDetailResponse, PortfolioListItem from app.schemas.portfolio import CommentResponse, PortfolioDetailResponse, PortfolioListItem
from app.schemas.user import UserResponse from app.schemas.user import UserResponse
@@ -163,6 +164,45 @@ def print_user_detail(console: Console, user: UserResponse) -> None:
) )
def print_daemon_table(console: Console, daemons: list[Daemon]) -> None:
table = Table(title="Daemons", expand=True)
table.add_column("ID", style="cyan", no_wrap=True)
table.add_column("Slug", no_wrap=True)
table.add_column("Display Name")
table.add_column("Active", no_wrap=True)
table.add_column("Created At", no_wrap=True)
table.add_column("Updated At", no_wrap=True)
for daemon in daemons:
table.add_row(
daemon.id,
daemon.daemon_slug,
daemon.display_name or "-",
"yes" if daemon.is_active else "no",
daemon.created_at.isoformat(timespec="seconds") if daemon.created_at else "N/A",
daemon.updated_at.isoformat(timespec="seconds") if daemon.updated_at else "N/A",
)
console.print(table)
def print_daemon_detail(console: Console, daemon: Daemon) -> None:
console.print(
Panel.fit(
"\n".join(
[
f"id: {daemon.id}",
f"owner_user_id: {daemon.owner_user_id}",
f"daemon_slug: {daemon.daemon_slug}",
f"display_name: {daemon.display_name or '-'}",
f"is_active: {daemon.is_active}",
f"created_at: {daemon.created_at.isoformat(timespec='seconds') if daemon.created_at else 'N/A'}",
f"updated_at: {daemon.updated_at.isoformat(timespec='seconds') if daemon.updated_at else 'N/A'}",
]
),
title="Daemon",
)
)
def print_service_token_table(console: Console, tokens: list[AuthTokenResponse]) -> None: def print_service_token_table(console: Console, tokens: list[AuthTokenResponse]) -> None:
table = Table(title="Service Tokens", expand=True) table = Table(title="Service Tokens", expand=True)
table.add_column("Name", style="cyan", no_wrap=True) table.add_column("Name", style="cyan", no_wrap=True)
@@ -235,6 +275,9 @@ def render_output(console: Console, output_format: str, payload: Any, *, empty_m
if not payload and empty_message: if not payload and empty_message:
console.print(empty_message) console.print(empty_message)
return return
if all(isinstance(item, Daemon) for item in payload):
print_daemon_table(console, payload)
return
if all(isinstance(item, PortfolioListItem) for item in payload): if all(isinstance(item, PortfolioListItem) for item in payload):
print_portfolio_table(console, payload) print_portfolio_table(console, payload)
return return
@@ -257,6 +300,10 @@ def render_output(console: Console, output_format: str, payload: Any, *, empty_m
print_user_detail(console, payload) print_user_detail(console, payload)
return return
if isinstance(payload, Daemon):
print_daemon_detail(console, payload)
return
if isinstance(payload, CommentResponse): if isinstance(payload, CommentResponse):
print_comment_detail(console, payload) print_comment_detail(console, payload)
return return
+25
View File
@@ -903,6 +903,31 @@ def test_cli_help_exposes_bot_token_commands() -> None:
assert "revoke" in result.stdout assert "revoke" in result.stdout
def test_cli_daemon_create_and_list_render_entries() -> None:
repo = FakeRepo(_portfolio())
repo.create_user("alice", permission="read")
runner = build_runner(repo)
create_result = runner.invoke(
runner.app,
["daemon", "create", "alpha-bot", "--user", "alice", "--display-name", "Alpha Bot"],
)
list_result = runner.invoke(
runner.app,
["daemon", "list", "--user", "alice"],
)
assert create_result.exit_code == 0
assert "Daemon" in create_result.stdout
assert "alpha-bot" in create_result.stdout
assert "Alpha Bot" in create_result.stdout
assert list_result.exit_code == 0
assert "Daemons" in list_result.stdout
assert "alpha-bot" in list_result.stdout
assert "Alpha Bot" in list_result.stdout
def test_cli_service_token_issue_supports_json() -> None: def test_cli_service_token_issue_supports_json() -> None:
repo = FakeRepo(_portfolio()) repo = FakeRepo(_portfolio())
user = repo.create_user("alice", permission="read") user = repo.create_user("alice", permission="read")