11"""Tests for CLI behavior."""
22
33from pathlib import Path
4+ from types import SimpleNamespace
45from typing import Any
56
67import pytest
8+ from rich .console import Console
79from typer .testing import CliRunner
810
911from librarian import cli
1012
13+ LONG_WINDOWS_PATH = (
14+ r"C:\Users\example\Documents\Codex\2026-05-16"
15+ r"\concurso-browser-deckbuilder\docs\planning"
16+ r"\vgf-56-revolt-fx-distribution-and-ui-editor-plan.md"
17+ )
18+ LONG_WINDOWS_FILENAME = "vgf-56-revolt-fx-distribution-and-ui-editor-plan.md"
19+
20+
21+ def assert_table_preserves_long_path_reference (result : Any ) -> None :
22+ assert result .exit_code == 0
23+ assert "\ufffd " not in result .output
24+ assert "…" not in result .output
25+ normalized_output = "" .join (
26+ char for char in result .output if char .isalnum () or char in "\\ /:._-"
27+ )
28+ assert LONG_WINDOWS_FILENAME in normalized_output
29+
30+
31+ def fake_document () -> SimpleNamespace :
32+ return SimpleNamespace (
33+ id = 1 ,
34+ title = "VGF-56 plan" ,
35+ path = LONG_WINDOWS_PATH ,
36+ asset_type = SimpleNamespace (value = "text" ),
37+ )
38+
1139
1240def test_add_directory_exits_nonzero_when_indexing_errors (
1341 tmp_path : Path , monkeypatch : pytest .MonkeyPatch
@@ -23,6 +51,7 @@ def test_add_directory_exits_nonzero_when_indexing_errors(
2351 monkeypatch .setattr (cli , "SOURCES_FILE" , config_dir / "sources.json" )
2452 monkeypatch .setattr (cli , "SETTINGS_FILE" , config_dir / "settings.json" )
2553 monkeypatch .setattr (cli , "_get_config" , lambda : {"ensure_directories" : lambda : None })
54+ monkeypatch .setattr (cli , "console" , Console (width = 500 , color_system = None ))
2655
2756 async def fake_server_ingest (context : Any , directory : str ) -> dict [str , Any ]:
2857 return {
@@ -31,7 +60,7 @@ async def fake_server_ingest(context: Any, directory: str) -> dict[str, Any]:
3160 "indexed" : 0 ,
3261 "updated" : 0 ,
3362 "skipped" : 0 ,
34- "errors" : [{"path" : str ( fixture ) , "error" : "parser exploded" }],
63+ "errors" : [{"path" : fixture . name , "error" : "parser exploded" }],
3564 "files" : [],
3665 }
3766
@@ -44,3 +73,128 @@ async def fake_server_ingest(context: Any, directory: str) -> dict[str, Any]:
4473 assert result .exit_code == 1
4574 assert "Errors:" in result .output
4675 assert "parser exploded" in result .output
76+
77+
78+ def test_search_table_wraps_long_windows_paths (
79+ monkeypatch : pytest .MonkeyPatch ,
80+ ) -> None :
81+ """Narrow table output should wrap paths instead of replacing them with ellipses."""
82+ monkeypatch .setattr (cli , "_get_config" , lambda : {"ensure_directories" : lambda : None })
83+ monkeypatch .setattr (cli , "console" , Console (width = 80 , color_system = None ))
84+
85+ async def fake_search_library (** kwargs : Any ) -> list [dict [str , Any ]]:
86+ return [
87+ {
88+ "score" : 1.0 ,
89+ "document_path" : LONG_WINDOWS_PATH ,
90+ "content" : "matched content" ,
91+ "heading_path" : None ,
92+ }
93+ ]
94+
95+ monkeypatch .setattr ("librarian.server.search_library" , fake_search_library )
96+
97+ result = CliRunner ().invoke (
98+ cli .app ,
99+ [
100+ "search" ,
101+ "VGF-56 UI editor Fabric Tweakpane Pixi Layout Pixi UI" ,
102+ "--format" ,
103+ "table" ,
104+ ],
105+ )
106+
107+ assert_table_preserves_long_path_reference (result )
108+
109+
110+ def test_list_table_wraps_long_windows_paths (monkeypatch : pytest .MonkeyPatch ) -> None :
111+ """Source listing should not truncate long paths with an ellipsis."""
112+ monkeypatch .setattr (cli , "console" , Console (width = 80 , color_system = None ))
113+ monkeypatch .setattr (
114+ cli ,
115+ "_load_sources" ,
116+ lambda : [{"name" : "docs" , "path" : LONG_WINDOWS_PATH , "is_file" : False }],
117+ )
118+
119+ result = CliRunner ().invoke (cli .app , ["list" ])
120+
121+ assert_table_preserves_long_path_reference (result )
122+
123+
124+ def test_docs_overview_wraps_long_windows_paths (monkeypatch : pytest .MonkeyPatch ) -> None :
125+ """Document source overview should wrap long paths."""
126+ monkeypatch .setattr (cli , "_get_config" , lambda : {"ensure_directories" : lambda : None })
127+ monkeypatch .setattr (cli , "console" , Console (width = 80 , color_system = None ))
128+ monkeypatch .setattr (
129+ cli ,
130+ "_load_sources" ,
131+ lambda : [{"name" : "docs" , "path" : LONG_WINDOWS_PATH , "is_file" : False }],
132+ )
133+ monkeypatch .setattr (
134+ "librarian.storage.database.get_database" ,
135+ lambda : SimpleNamespace (list_documents = lambda : [fake_document ()]),
136+ )
137+
138+ result = CliRunner ().invoke (cli .app , ["docs" ])
139+
140+ assert_table_preserves_long_path_reference (result )
141+
142+
143+ def test_docs_list_table_wraps_long_windows_paths (monkeypatch : pytest .MonkeyPatch ) -> None :
144+ """Document listing should wrap long paths in table output."""
145+ monkeypatch .setattr (cli , "_get_config" , lambda : {"ensure_directories" : lambda : None })
146+ monkeypatch .setattr (cli , "console" , Console (width = 80 , color_system = None ))
147+ monkeypatch .setattr (
148+ "librarian.storage.database.get_database" ,
149+ lambda : SimpleNamespace (list_documents = lambda : [fake_document ()]),
150+ )
151+
152+ result = CliRunner ().invoke (cli .app , ["docs" , "list" , "--format" , "table" ])
153+
154+ assert_table_preserves_long_path_reference (result )
155+
156+
157+ def test_docs_search_table_wraps_long_windows_paths (monkeypatch : pytest .MonkeyPatch ) -> None :
158+ """Document title search should wrap long paths in table output."""
159+ monkeypatch .setattr (cli , "_get_config" , lambda : {"ensure_directories" : lambda : None })
160+ monkeypatch .setattr (cli , "console" , Console (width = 80 , color_system = None ))
161+ monkeypatch .setattr (
162+ "librarian.storage.database.get_database" ,
163+ lambda : SimpleNamespace (list_documents = lambda : [fake_document ()]),
164+ )
165+
166+ result = CliRunner ().invoke (cli .app , ["docs" , "search" , "VGF" , "--format" , "table" ])
167+
168+ assert_table_preserves_long_path_reference (result )
169+
170+
171+ def test_search_paths_outputs_complete_long_windows_paths (
172+ monkeypatch : pytest .MonkeyPatch ,
173+ ) -> None :
174+ """Paths output remains the copyable full-path mode for search results."""
175+ monkeypatch .setattr (cli , "_get_config" , lambda : {"ensure_directories" : lambda : None })
176+
177+ async def fake_search_library (** kwargs : Any ) -> list [dict [str , Any ]]:
178+ return [
179+ {
180+ "score" : 1.0 ,
181+ "document_path" : LONG_WINDOWS_PATH ,
182+ "content" : "matched content" ,
183+ "heading_path" : None ,
184+ }
185+ ]
186+
187+ monkeypatch .setattr ("librarian.server.search_library" , fake_search_library )
188+
189+ result = CliRunner ().invoke (
190+ cli .app ,
191+ [
192+ "search" ,
193+ "VGF-56 UI editor Fabric Tweakpane Pixi Layout Pixi UI" ,
194+ "--format" ,
195+ "paths" ,
196+ ],
197+ )
198+
199+ assert result .exit_code == 0
200+ assert result .output == f"{ LONG_WINDOWS_PATH } \n "
0 commit comments