Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions librarian/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ def list_sources(

table = Table(title="Document Sources", show_header=True, header_style="bold cyan")
table.add_column("Name", style="green")
table.add_column("Path", style="blue")
table.add_column("Path", style="blue", overflow="fold")
table.add_column("Type", style="magenta")
table.add_column("Status", style="yellow")

Expand Down Expand Up @@ -1029,7 +1029,7 @@ def docs_overview(ctx: typer.Context) -> None:

table = Table(title="Sources", show_header=True, header_style="bold cyan")
table.add_column("Name", style="green")
table.add_column("Path", style="blue")
table.add_column("Path", style="blue", overflow="fold")
table.add_column("Docs", justify="right", style="yellow")
table.add_column("Status")

Expand Down Expand Up @@ -1102,7 +1102,7 @@ def docs_list(
)
table.add_column("ID", style="dim", width=4)
table.add_column("Title", style="green", max_width=38)
table.add_column("Path", style="blue", max_width=46)
table.add_column("Path", style="blue", max_width=46, overflow="fold")
table.add_column("Type", style="magenta", width=6)

home = str(Path.home())
Expand Down Expand Up @@ -1161,7 +1161,7 @@ def docs_search(
)
table.add_column("ID", style="dim", width=4)
table.add_column("Title", style="green")
table.add_column("Path", style="blue", max_width=50)
table.add_column("Path", style="blue", max_width=50, overflow="fold")

home = str(Path.home())
for doc in matches[:limit]:
Expand Down Expand Up @@ -1331,7 +1331,7 @@ def search_cmd(
table = Table(show_header=True, header_style="bold cyan", box=None)
table.add_column("#", style="dim", width=3)
table.add_column("Score", justify="right", width=7)
table.add_column("Path", style="blue")
table.add_column("Path", style="blue", overflow="fold")

for i, result in enumerate(results, 1):
score = result.get("score", 0)
Expand Down
156 changes: 155 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
"""Tests for CLI behavior."""

from pathlib import Path
from types import SimpleNamespace
from typing import Any

import pytest
from rich.console import Console
from typer.testing import CliRunner

from librarian import cli

LONG_WINDOWS_PATH = (
r"C:\Users\example\Documents\Codex\2026-05-16"
r"\concurso-browser-deckbuilder\docs\planning"
r"\vgf-56-revolt-fx-distribution-and-ui-editor-plan.md"
)
LONG_WINDOWS_FILENAME = "vgf-56-revolt-fx-distribution-and-ui-editor-plan.md"


def assert_table_preserves_long_path_reference(result: Any) -> None:
assert result.exit_code == 0
assert "\ufffd" not in result.output
assert "…" not in result.output
normalized_output = "".join(
char for char in result.output if char.isalnum() or char in "\\/:._-"
)
assert LONG_WINDOWS_FILENAME in normalized_output


def fake_document() -> SimpleNamespace:
return SimpleNamespace(
id=1,
title="VGF-56 plan",
path=LONG_WINDOWS_PATH,
asset_type=SimpleNamespace(value="text"),
)


def test_add_directory_exits_nonzero_when_indexing_errors(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
Expand All @@ -23,6 +51,7 @@ def test_add_directory_exits_nonzero_when_indexing_errors(
monkeypatch.setattr(cli, "SOURCES_FILE", config_dir / "sources.json")
monkeypatch.setattr(cli, "SETTINGS_FILE", config_dir / "settings.json")
monkeypatch.setattr(cli, "_get_config", lambda: {"ensure_directories": lambda: None})
monkeypatch.setattr(cli, "console", Console(width=500, color_system=None))

async def fake_server_ingest(context: Any, directory: str) -> dict[str, Any]:
return {
Expand All @@ -31,7 +60,7 @@ async def fake_server_ingest(context: Any, directory: str) -> dict[str, Any]:
"indexed": 0,
"updated": 0,
"skipped": 0,
"errors": [{"path": str(fixture), "error": "parser exploded"}],
"errors": [{"path": fixture.name, "error": "parser exploded"}],
"files": [],
}

Expand All @@ -44,3 +73,128 @@ async def fake_server_ingest(context: Any, directory: str) -> dict[str, Any]:
assert result.exit_code == 1
assert "Errors:" in result.output
assert "parser exploded" in result.output


def test_search_table_wraps_long_windows_paths(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Narrow table output should wrap paths instead of replacing them with ellipses."""
monkeypatch.setattr(cli, "_get_config", lambda: {"ensure_directories": lambda: None})
monkeypatch.setattr(cli, "console", Console(width=80, color_system=None))

async def fake_search_library(**kwargs: Any) -> list[dict[str, Any]]:
return [
{
"score": 1.0,
"document_path": LONG_WINDOWS_PATH,
"content": "matched content",
"heading_path": None,
}
]

monkeypatch.setattr("librarian.server.search_library", fake_search_library)

result = CliRunner().invoke(
cli.app,
[
"search",
"VGF-56 UI editor Fabric Tweakpane Pixi Layout Pixi UI",
"--format",
"table",
],
)

assert_table_preserves_long_path_reference(result)


def test_list_table_wraps_long_windows_paths(monkeypatch: pytest.MonkeyPatch) -> None:
"""Source listing should not truncate long paths with an ellipsis."""
monkeypatch.setattr(cli, "console", Console(width=80, color_system=None))
monkeypatch.setattr(
cli,
"_load_sources",
lambda: [{"name": "docs", "path": LONG_WINDOWS_PATH, "is_file": False}],
)

result = CliRunner().invoke(cli.app, ["list"])

assert_table_preserves_long_path_reference(result)


def test_docs_overview_wraps_long_windows_paths(monkeypatch: pytest.MonkeyPatch) -> None:
"""Document source overview should wrap long paths."""
monkeypatch.setattr(cli, "_get_config", lambda: {"ensure_directories": lambda: None})
monkeypatch.setattr(cli, "console", Console(width=80, color_system=None))
monkeypatch.setattr(
cli,
"_load_sources",
lambda: [{"name": "docs", "path": LONG_WINDOWS_PATH, "is_file": False}],
)
monkeypatch.setattr(
"librarian.storage.database.get_database",
lambda: SimpleNamespace(list_documents=lambda: [fake_document()]),
)

result = CliRunner().invoke(cli.app, ["docs"])

assert_table_preserves_long_path_reference(result)


def test_docs_list_table_wraps_long_windows_paths(monkeypatch: pytest.MonkeyPatch) -> None:
"""Document listing should wrap long paths in table output."""
monkeypatch.setattr(cli, "_get_config", lambda: {"ensure_directories": lambda: None})
monkeypatch.setattr(cli, "console", Console(width=80, color_system=None))
monkeypatch.setattr(
"librarian.storage.database.get_database",
lambda: SimpleNamespace(list_documents=lambda: [fake_document()]),
)

result = CliRunner().invoke(cli.app, ["docs", "list", "--format", "table"])

assert_table_preserves_long_path_reference(result)


def test_docs_search_table_wraps_long_windows_paths(monkeypatch: pytest.MonkeyPatch) -> None:
"""Document title search should wrap long paths in table output."""
monkeypatch.setattr(cli, "_get_config", lambda: {"ensure_directories": lambda: None})
monkeypatch.setattr(cli, "console", Console(width=80, color_system=None))
monkeypatch.setattr(
"librarian.storage.database.get_database",
lambda: SimpleNamespace(list_documents=lambda: [fake_document()]),
)

result = CliRunner().invoke(cli.app, ["docs", "search", "VGF", "--format", "table"])

assert_table_preserves_long_path_reference(result)


def test_search_paths_outputs_complete_long_windows_paths(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Paths output remains the copyable full-path mode for search results."""
monkeypatch.setattr(cli, "_get_config", lambda: {"ensure_directories": lambda: None})

async def fake_search_library(**kwargs: Any) -> list[dict[str, Any]]:
return [
{
"score": 1.0,
"document_path": LONG_WINDOWS_PATH,
"content": "matched content",
"heading_path": None,
}
]

monkeypatch.setattr("librarian.server.search_library", fake_search_library)

result = CliRunner().invoke(
cli.app,
[
"search",
"VGF-56 UI editor Fabric Tweakpane Pixi Layout Pixi UI",
"--format",
"paths",
],
)

assert result.exit_code == 0
assert result.output == f"{LONG_WINDOWS_PATH}\n"
Loading