Skip to content

Commit 98b34d4

Browse files
committed
Reduce test_cli.py duplication via shared capture fixture
The per-test fake_run_search / fake_shutdown / monkeypatch boilerplate was the single biggest duplication source (118 dup lines, ~13% file density). Upgrade the existing patched_pipeline fixture to also capture the Query, and route the four query-asserting tests (source-default / exclude-source / min-citations / top-tier) through it instead of each re-rolling its own fake. -35 lines; the two async-stub NOSONARs now live once in the fixture.
1 parent b6df24f commit 98b34d4

1 file changed

Lines changed: 25 additions & 60 deletions

File tree

tests/test_cli.py

Lines changed: 25 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,28 @@ async def _fake_success(collection, out_dir):
4343

4444
@pytest.fixture()
4545
def patched_pipeline(monkeypatch, sample_papers):
46-
async def fake_run_search(query: Query, **_kwargs) -> PaperCollection:
46+
"""Stub cli's run_search + shutdown_clients and return a dict that captures
47+
the Query passed to run_search (``patched_pipeline["query"]``).
48+
49+
Tests that only need the pipeline stubbed ignore the return value; tests
50+
that assert on the constructed Query read ``patched_pipeline["query"]``
51+
instead of each re-rolling their own ``fake_run_search`` / ``fake_shutdown``
52+
boilerplate.
53+
"""
54+
captured: dict[str, Query] = {}
55+
56+
async def fake_run_search(query, **_kwargs): # NOSONAR async stub
57+
captured["query"] = query
4758
return PaperCollection(query=query, papers=tuple(sample_papers))
4859

49-
async def fake_shutdown() -> None:
60+
async def fake_shutdown(): # NOSONAR async stub
5061
return None
5162

5263
monkeypatch.setattr(cli_module, "run_search", fake_run_search)
5364
monkeypatch.setattr(cli_module, "shutdown_clients", fake_shutdown)
5465
# Leave the autouse ``_stub_download_pdfs`` in place so the new per-paper
5566
# PPT gate sees every paper as downloadable.
67+
return captured
5668

5769

5870
def test_cli_runs_end_to_end(tmp_path: Path, patched_pipeline, capsys):
@@ -248,28 +260,16 @@ def test_cli_rejects_doi_identifier_until_resolver_lands(tmp_path):
248260
assert code == 2
249261

250262

251-
def test_cli_source_default_is_multi_source(tmp_path, monkeypatch, sample_papers):
263+
def test_cli_source_default_is_multi_source(tmp_path, patched_pipeline):
252264
"""When --source is omitted, run_search must be invoked across the
253265
DEFAULT_SOURCES mix, not just arxiv."""
254266
from thesisagents.core.constants import DEFAULT_SOURCES
255267

256-
captured: dict[str, Query] = {}
257-
258-
async def fake_run_search(query: Query, **_kwargs) -> PaperCollection:
259-
captured["query"] = query
260-
return PaperCollection(query=query, papers=tuple(sample_papers))
261-
262-
async def fake_shutdown() -> None:
263-
return None
264-
265-
monkeypatch.setattr(cli_module, "run_search", fake_run_search)
266-
monkeypatch.setattr(cli_module, "shutdown_clients", fake_shutdown)
267-
268268
code = cli_module.main(
269269
["--query", "x", "--out", str(tmp_path), "--export", "bib"]
270270
)
271271
assert code == 0
272-
assert captured["query"].sources == DEFAULT_SOURCES
272+
assert patched_pipeline["query"].sources == DEFAULT_SOURCES
273273

274274

275275
def test_cli_list_sources(capsys):
@@ -293,53 +293,30 @@ def test_cli_list_exports(capsys):
293293
assert "pptx" in out
294294

295295

296-
def test_cli_exclude_source_prunes_default_mix(tmp_path, monkeypatch, sample_papers):
296+
def test_cli_exclude_source_prunes_default_mix(tmp_path, patched_pipeline):
297297
"""--exclude-source subtracts from the resolved mix; the no-VPN path drops
298298
only ieee and keeps every other default source."""
299299
from thesisagents.core.constants import DEFAULT_SOURCES
300300

301-
captured: dict[str, Query] = {}
302-
303-
async def fake_run_search(query: Query, **_kwargs) -> PaperCollection: # NOSONAR async stub
304-
captured["query"] = query
305-
return PaperCollection(query=query, papers=tuple(sample_papers))
306-
307-
async def fake_shutdown() -> None: # NOSONAR async stub
308-
return None
309-
310-
monkeypatch.setattr(cli_module, "run_search", fake_run_search)
311-
monkeypatch.setattr(cli_module, "shutdown_clients", fake_shutdown)
312-
313301
code = cli_module.main(
314302
["--query", "x", "--out", str(tmp_path), "--export", "bib",
315303
"--exclude-source", "ieee"]
316304
)
317305
assert code == 0
318-
assert "ieee" not in captured["query"].sources
306+
assert "ieee" not in patched_pipeline["query"].sources
319307
expected = tuple(s for s in DEFAULT_SOURCES if s != "ieee")
320-
assert captured["query"].sources == expected
308+
assert patched_pipeline["query"].sources == expected
321309

322310

323-
def test_cli_min_citations_flows_into_query(tmp_path, monkeypatch, sample_papers):
311+
def test_cli_min_citations_flows_into_query(tmp_path, patched_pipeline):
324312
"""--min-citations is parsed and passed through to the Query (it was
325313
previously unreachable from the CLI)."""
326-
captured: dict[str, Query] = {}
327-
328-
async def fake_run_search(query: Query, **_kwargs) -> PaperCollection: # NOSONAR async stub
329-
captured["query"] = query
330-
return PaperCollection(query=query, papers=tuple(sample_papers))
331-
332-
async def fake_shutdown() -> None: # NOSONAR async stub
333-
return None
334-
335-
monkeypatch.setattr(cli_module, "run_search", fake_run_search)
336-
monkeypatch.setattr(cli_module, "shutdown_clients", fake_shutdown)
337314
code = cli_module.main(
338315
["--query", "x", "--out", str(tmp_path), "--export", "bib",
339316
"--min-citations", "50"]
340317
)
341318
assert code == 0
342-
assert captured["query"].min_citations == 50
319+
assert patched_pipeline["query"].min_citations == 50
343320

344321

345322
def test_cli_exclude_unknown_source_errors(tmp_path):
@@ -359,33 +336,21 @@ def test_cli_exclude_all_sources_errors(tmp_path):
359336
)
360337

361338

362-
def test_cli_top_tier_filter_off_by_default(tmp_path, monkeypatch, sample_papers):
339+
def test_cli_top_tier_filter_off_by_default(tmp_path, patched_pipeline):
363340
"""top_tier_only is OFF by default (broader coverage including IEEE / ACM
364341
workshops); --top-tier-only flips it on."""
365-
captured: dict[str, Query] = {}
366-
367-
async def fake_run_search(query: Query, **_kwargs) -> PaperCollection:
368-
captured["query"] = query
369-
return PaperCollection(query=query, papers=tuple(sample_papers))
370-
371-
async def fake_shutdown() -> None:
372-
return None
373-
374-
monkeypatch.setattr(cli_module, "run_search", fake_run_search)
375-
monkeypatch.setattr(cli_module, "shutdown_clients", fake_shutdown)
376-
377342
code = cli_module.main(
378343
["--query", "x", "--out", str(tmp_path), "--export", "bib"]
379344
)
380345
assert code == 0
381-
assert captured["query"].top_tier_only is False
346+
assert patched_pipeline["query"].top_tier_only is False
382347

383-
captured.clear()
348+
patched_pipeline.clear()
384349
code = cli_module.main(
385350
["--query", "x", "--top-tier-only", "--out", str(tmp_path), "--export", "bib"]
386351
)
387352
assert code == 0
388-
assert captured["query"].top_tier_only is True
353+
assert patched_pipeline["query"].top_tier_only is True
389354

390355

391356
def test_cli_default_triggers_pdf_download(tmp_path, monkeypatch, sample_papers):

0 commit comments

Comments
 (0)