← back to Paul Conrad Archive
tests/test_ia_fulltext.py
136 lines
"""IA full-text appearance parser + the base-client truncation fix (TK-12199)."""
from conftest import FakeHttp
from conrad.crawlers import base, ia_fulltext
from conrad.crawlers.ia_fulltext import classify, issue_date
CREDIT = ["Views / A portfolio from around the nation\n{{{Paul Conrad}}}\nThe Los Angeles Times\nLos Angeles Times Syndicate"]
CAPS = ["OKAY ... LET'S MOVE IT\nPAUL {{{CONRAD}}} Los Angeles Times © Los Angeles Times Syndicate"]
PROSE = ["commissions was for {{{Paul Conrad}}}, editorial cartoonist for the Los Angeles Times Syndicate. He wanted a Richard"]
OTHER = ["Pricing Flukes Create Asia Arbitrage Play\nBy {{{Conrad}}} de Aenlle\nInternational Herald Tribune"]
JOSEPH = ["Lord Jim and other tales. Joseph {{{Conrad}}}\n© Los Angeles Times Syndicate"]
def test_classify_credit_lines():
assert classify(CREDIT)[0] == "credit"
kind, ev = classify(CAPS)
assert kind == "credit" and "Los Angeles Times" in ev
def test_classify_rejects_prose_and_other_conrads():
# negative cases: an article ABOUT Conrad (long prose line) and other Conrads must never become records
assert classify(PROSE)[0] == "mention"
assert classify(OTHER)[0] == "other_conrad"
assert classify(JOSEPH)[0] == "other_conrad"
assert classify([])[0] == "mention"
def test_issue_date():
assert issue_date({"file_basename": "Aug 11 1998, The Jerusalem Post, #20003, Israel (en)"}) == ("1998-08-11", 1998)
assert issue_date({"identifier": "The_Times_News_Idaho_Newspaper_1978_11_01"}) == ("1978-11-01", 1978)
assert issue_date({"identifier": "x", "year": 1975}) == (None, 1975)
def _fts_page(hits):
import json
return json.dumps({"response": {"body": {"hits": {"total": len(hits), "hits": hits}}}})
def test_crawl_saves_only_credit_pages(tmpdb):
hits = [
{"fields": {"identifier": "JP1980", "file_basename": "Mar 3 1980, The Jerusalem Post Magazine", "page_num": 7,
"title": "The Jerusalem Post Magazine , 1980, Israel, English", "year": 1980,
"collection": ["newspapers"], "result_in_subfile": True}, "highlight": {"text": CREDIT}},
{"fields": {"identifier": "IHT1998", "file_basename": "May 7 1998, IHT", "page_num": 3, "year": 1998,
"title": "International Herald Tribune , 1998", "collection": ["newspapers"]},
"highlight": {"text": OTHER}},
{"fields": {"identifier": "bestcartoons1975", "page_num": 40, "year": 1975, "title": "Best Editorial Cartoons",
"collection": ["inlibrary", "internetarchivebooks"]}, "highlight": {"text": CAPS}},
]
http = FakeHttp({"page_production": (200, _fts_page(hits)),
"iiif.archive.org/iiif/3/JP1980/manifest.json": (200, '{"items": []}')})
c = ia_fulltext.CRAWLER(conn=tmpdb, http=http, years=[1980], variants=[("t", '"Paul Conrad"', range(1980, 1981))])
res = c.run()
rows = tmpdb.execute("SELECT c.title, c.date_exact, c.publication, cs.image_url FROM cartoons c "
"JOIN cartoon_sources cs ON cs.cartoon_id=c.id ORDER BY c.title").fetchall()
assert len(rows) == 2, rows
news = [r for r in rows if r["date_exact"] == "1980-03-03"][0]
assert news["publication"] == "The Jerusalem Post Magazine" and news["title"].startswith("[Paul Conrad cartoon")
book = [r for r in rows if r["date_exact"] is None][0]
assert book["image_url"] is None # lending-library book: no image URL recorded
# the lending-library book never asked IA for a manifest
assert not any("bestcartoons1975/manifest" in u for u in http.calls)
assert "other Conrads=1" in res["notes"]
# ---- base client: the whole body must be read through ONE iter_content generator
class _BufferingResp:
"""Mimics a gzip response: a generator drains the raw stream into its own buffer on first use, so a SECOND
iter_content() call sees nothing (the pre-fix code truncated bodies to the first chunk this way)."""
status_code = 200
headers = {"content-type": "text/html; charset=utf-8"}
encoding = "utf-8"
def __init__(self, body: bytes):
self._raw = [body]
def iter_content(self, n):
buf = b"".join(self._raw)
self._raw = []
for i in range(0, len(buf), n):
yield buf[i:i + n]
def close(self):
pass
@property
def content(self):
return self._content
@property
def text(self):
return self._content.decode("utf-8")
def test_fetch_reads_whole_body_with_one_generator(monkeypatch):
body = b"<!DOCTYPE html>" + b"x" * 200000
h = base.Http(use_cache=False)
monkeypatch.setattr(h.s, "get", lambda *a, **k: _BufferingResp(body))
monkeypatch.setattr(base.config, "REQUEST_DELAY", 0)
monkeypatch.setattr(h, "delay_for", lambda url: 0)
st, text = h._fetch("https://example.org/page", None)
assert st == 200 and len(text) == len(body)
def test_fetch_negative_two_generators_would_truncate():
# negative control: the buffering fake really does lose data when iter_content is called twice
r = _BufferingResp(b"<!DOCTYPE html>" + b"x" * 1000)
head = next(r.iter_content(16))
rest = b"".join(r.iter_content(65536))
assert len(head + rest) == 16
def test_cartoon_title_and_more_negatives():
from conrad.crawlers.ia_fulltext import cartoon_title
assert cartoon_title('noted. "AT YOUR SERVICE, MADAM" was the title of this cartoon by Paul Conrad') == \
"AT YOUR SERVICE, MADAM"
assert cartoon_title("by Dennis Renault 20 The View from Watts. By Paul Conrad. Los Angeles Times 34") == \
"The View from Watts"
assert cartoon_title("Paul Conrad, Los Angeles Times") is None
# negatives: a music copyright entry, a prize roster and a name list are not cartoon appearances
assert classify(["Wallis; 5Aug59.\nCHINA CLIPPER; m {{{Paul Conrad}}}. -\n© Lo ridge Music Inc."])[0] == "mention"
assert classify(["Frank Miller, Des Moines Register.\n1964 — {{{Paul Conrad}}}, Denver Post.\n1966 — Don Wright"])[0] \
== "mention"
assert classify(["Warren Christophel\n{{{Paul Conrad}}}\nMrs. Chauncey Crossgrove"])[0] == "mention"
def test_editor_publisher_syndicate_ad():
from conrad.crawlers.ia_fulltext import classify_hit
ep = {"identifier": "sim_editor-publisher_1970-08-01_103_31"}
ad = ["Thought-Provoking Editorial Cartoons\n{{{PAUL CONRAD}}}\nFive cartoons a week\nLos Angeles Times Syndicate"]
assert classify_hit(ep, ad)[0] == "syndicate_ad"
# negatives: the same ad text outside E&P, and an E&P prize roster, are not syndicate ads
assert classify_hit({"identifier": "other_1970"}, ad)[0] == "mention"
roster = ["Pulitzer Prize winners\n{{{Paul Conrad}}}\nLos Angeles Times Syndicate cartoons"]
assert classify_hit(ep, roster)[0] == "mention"