Skip to content

Commit febebfc

Browse files
committed
test: add fetcher unit tests (IDs 27, 28, 33, 34)
Add tests for parse_page, _extract_abstract, _extract_published_version, _normalize_date, and _parse_search_result. Also fix a bug in _parse_search_result where authors=None caused TypeError: 'NoneType' object is not iterable.
1 parent e5d0c7e commit febebfc

2 files changed

Lines changed: 189 additions & 1 deletion

File tree

src/nber_cli/fetcher.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,8 @@ def _parse_search_result(raw_result: dict[str, Any]) -> NBER:
276276
paper_id_match = re.search(r"/papers/w(\d+)", url)
277277
paper_id = int(paper_id_match.group(1)) if paper_id_match else 0
278278
full_url = f"{_NBER_BASE_URL}{url}" if url.startswith("/") else url
279-
authors = [_clean_html_text(author) for author in raw_result.get("authors", [])]
279+
authors_raw = raw_result.get("authors", []) or []
280+
authors = [_clean_html_text(author) for author in authors_raw]
280281

281282
return NBER(
282283
paper_id=paper_id,

tests/test_fetcher.py

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,190 @@ def fake_urlopen(request, timeout, context=None):
136136

137137
assert text == "ok"
138138
assert mock_sleep.call_count == 2
139+
140+
141+
class TestParsePage:
142+
def test_parses_full_page(self):
143+
page = """<html><head>
144+
<meta name="citation_title" content="Test Paper Title">
145+
<meta name="citation_author" content="Author One">
146+
<meta name="citation_author" content="Author Two">
147+
<meta name="citation_publication_date" content="2024/01/15">
148+
<meta name="citation_technical_report_number" content="w32000">
149+
</head><body>
150+
<div class="page-header__intro-inner"> <p>This is the <em>abstract</em> of the paper.</p></div>
151+
<h2>Published Versions</h2> <p>Published in <em>Journal of Economics</em>, 2024.</p>
152+
</body></html>"""
153+
from nber_cli.fetcher import parse_page
154+
result = parse_page(page)
155+
assert result.paper_id == 32000
156+
assert result.title == "Test Paper Title"
157+
assert result.authors == ["Author One", "Author Two"]
158+
assert result.date == "2024/01/15"
159+
assert result.abstract == "This is the abstract of the paper."
160+
assert result.published_version == "Published in Journal of Economics, 2024."
161+
162+
def test_handles_missing_fields(self):
163+
page = "<html><head></head><body></body></html>"
164+
from nber_cli.fetcher import parse_page
165+
result = parse_page(page)
166+
assert result.paper_id == 0
167+
assert result.title == ""
168+
assert result.authors == []
169+
assert result.date == ""
170+
assert result.abstract == ""
171+
assert result.published_version is None
172+
173+
def test_parses_paper_id_with_leading_zeros(self):
174+
page = '<meta name="citation_technical_report_number" content="w00123">'
175+
from nber_cli.fetcher import parse_page
176+
result = parse_page(page)
177+
assert result.paper_id == 123
178+
179+
def test_parses_paper_id_without_w_prefix(self):
180+
page = '<meta name="citation_technical_report_number" content="456">'
181+
from nber_cli.fetcher import parse_page
182+
result = parse_page(page)
183+
assert result.paper_id == 456
184+
185+
186+
class TestExtractAbstract:
187+
def test_extracts_abstract(self):
188+
from nber_cli.fetcher import _extract_abstract
189+
page = '<div class="page-header__intro-inner">\n<p>This is the abstract.</p>\n</div>'
190+
assert _extract_abstract(page) == "This is the abstract."
191+
192+
def test_strips_html_tags(self):
193+
from nber_cli.fetcher import _extract_abstract
194+
page = '<div class="page-header__intro-inner"><p>Abstract with <em>markup</em> and <a href="#">links</a>.</p></div>'
195+
assert _extract_abstract(page) == "Abstract with markup and links."
196+
197+
def test_collapses_whitespace(self):
198+
from nber_cli.fetcher import _extract_abstract
199+
page = '<div class="page-header__intro-inner"><p>Abstract\n\nwith spaces.</p></div>'
200+
assert _extract_abstract(page) == "Abstract with spaces."
201+
202+
def test_returns_empty_when_missing(self):
203+
from nber_cli.fetcher import _extract_abstract
204+
assert _extract_abstract("<html></html>") == ""
205+
206+
207+
class TestExtractPublishedVersion:
208+
def test_extracts_published_version(self):
209+
from nber_cli.fetcher import _extract_published_version
210+
page = '<h2>Published Versions</h2>\n<p>Published in Journal, 2024.</p>'
211+
assert _extract_published_version(page) == "Published in Journal, 2024."
212+
213+
def test_strips_html_tags(self):
214+
from nber_cli.fetcher import _extract_published_version
215+
page = '<h2>Published Versions</h2><p>Published in <em>Journal</em>.</p>'
216+
assert _extract_published_version(page) == "Published in Journal."
217+
218+
def test_returns_none_when_missing(self):
219+
from nber_cli.fetcher import _extract_published_version
220+
assert _extract_published_version("<html></html>") is None
221+
222+
223+
class TestNormalizeDate:
224+
def test_none_returns_none(self):
225+
from nber_cli.fetcher import _normalize_date
226+
assert _normalize_date(None) is None
227+
228+
def test_date_object_returns_isoformat(self):
229+
from nber_cli.fetcher import _normalize_date
230+
assert _normalize_date(date(2024, 1, 15)) == "2024-01-15"
231+
232+
def test_valid_string_returns_isoformat(self):
233+
from nber_cli.fetcher import _normalize_date
234+
assert _normalize_date("2024-01-15") == "2024-01-15"
235+
236+
def test_rejects_invalid_format(self):
237+
from nber_cli.fetcher import _normalize_date
238+
with pytest.raises(ValueError, match="expected YYYY-MM-DD"):
239+
_normalize_date("01/15/2024")
240+
241+
def test_rejects_malformed_date(self):
242+
from nber_cli.fetcher import _normalize_date
243+
with pytest.raises(ValueError, match="expected YYYY-MM-DD"):
244+
_normalize_date("2024-13-01")
245+
246+
def test_rejects_empty_string(self):
247+
from nber_cli.fetcher import _normalize_date
248+
with pytest.raises(ValueError, match="expected YYYY-MM-DD"):
249+
_normalize_date("")
250+
251+
252+
class TestParseSearchResult:
253+
def test_parses_complete_result(self):
254+
from nber_cli.fetcher import _parse_search_result
255+
raw = {
256+
"url": "/papers/w12345",
257+
"title": "A Paper",
258+
"authors": ['<a href="/people/a">Author A</a>'],
259+
"displaydate": "Jan 2024",
260+
"abstract": "Abstract text.",
261+
}
262+
result = _parse_search_result(raw)
263+
assert result.paper_id == 12345
264+
assert result.title == "A Paper"
265+
assert result.authors == ["Author A"]
266+
assert result.date == "Jan 2024"
267+
assert result.abstract == "Abstract text."
268+
assert result.url == "https://www.nber.org/papers/w12345"
269+
270+
def test_handles_missing_url(self):
271+
from nber_cli.fetcher import _parse_search_result
272+
result = _parse_search_result({})
273+
assert result.paper_id == 0
274+
assert result.url == ""
275+
276+
def test_handles_missing_fields_gracefully(self):
277+
from nber_cli.fetcher import _parse_search_result
278+
raw = {"url": "/papers/w99999"}
279+
result = _parse_search_result(raw)
280+
assert result.paper_id == 99999
281+
assert result.title == ""
282+
assert result.authors == []
283+
assert result.date == ""
284+
assert result.abstract == ""
285+
286+
def test_skips_empty_authors(self):
287+
from nber_cli.fetcher import _parse_search_result
288+
raw = {"authors": ["", "Author A", ""]}
289+
result = _parse_search_result(raw)
290+
assert result.authors == ["Author A"]
291+
292+
def test_handles_external_url(self):
293+
from nber_cli.fetcher import _parse_search_result
294+
raw = {"url": "https://example.com/papers/w1"}
295+
result = _parse_search_result(raw)
296+
assert result.url == "https://example.com/papers/w1"
297+
298+
def test_handles_none_values(self):
299+
from nber_cli.fetcher import _parse_search_result
300+
raw = {
301+
"url": None,
302+
"title": None,
303+
"authors": None,
304+
"displaydate": None,
305+
"abstract": None,
306+
}
307+
result = _parse_search_result(raw)
308+
assert result.paper_id == 0
309+
assert result.title == ""
310+
assert result.authors == []
311+
assert result.date == ""
312+
assert result.abstract == ""
313+
314+
def test_cleans_html_in_all_fields(self):
315+
from nber_cli.fetcher import _parse_search_result
316+
raw = {
317+
"title": "Title with <em>markup</em>",
318+
"authors": ['<a href="#">A &amp; B</a>'],
319+
"displaydate": "Jan 2024",
320+
"abstract": "Abstract with <br>line break.",
321+
}
322+
result = _parse_search_result(raw)
323+
assert result.title == "Title with markup"
324+
assert result.authors == ["A & B"]
325+
assert result.abstract == "Abstract with line break."

0 commit comments

Comments
 (0)