@@ -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 \n with 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 & 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