refactor: streamline TikTok and Instagram scraping logic by removing search_queries and enhancing documentation for clarity

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-07-13 17:11:25 -07:00
parent e8b3692b54
commit 2b018c4474
111 changed files with 1800 additions and 1580 deletions

View file

@ -52,9 +52,7 @@ async def test_acquire_token_local_mode_posts_desktop_login_json():
200, json={"access_token": "T", "refresh_token": "R", "token_type": "bearer"}
)
)
config = _make_config(
surfsense_user_email="u@example.com", surfsense_user_password="pw"
)
config = _make_config(surfsense_user_email="u@example.com", surfsense_user_password="pw")
bundle = await acquire_token(config)
assert bundle.access_token == "T"
assert bundle.refresh_token == "R"

View file

@ -94,10 +94,18 @@ async def test_documents_status_parses_state(respx_mock, http):
200,
json={
"items": [
{"id": 1, "title": "a.pdf", "document_type": "FILE",
"status": {"state": "ready", "reason": None}},
{"id": 2, "title": "b.pdf", "document_type": "FILE",
"status": {"state": "failed", "reason": "ETL boom"}},
{
"id": 1,
"title": "a.pdf",
"document_type": "FILE",
"status": {"state": "ready", "reason": None},
},
{
"id": 2,
"title": "b.pdf",
"document_type": "FILE",
"status": {"state": "failed", "reason": "ETL boom"},
},
]
},
)
@ -137,14 +145,26 @@ async def test_documents_upload_returns_payload(respx_mock, http, tmp_path: Path
async def test_documents_list_chunks_paginated(respx_mock, http):
respx_mock.get("/api/v1/documents/5/chunks").mock(
side_effect=[
httpx.Response(200, json={
"items": [{"id": 1, "content": "a"}, {"id": 2, "content": "b"}],
"total": 3, "page": 0, "page_size": 2, "has_more": True,
}),
httpx.Response(200, json={
"items": [{"id": 3, "content": "c"}],
"total": 3, "page": 1, "page_size": 2, "has_more": False,
}),
httpx.Response(
200,
json={
"items": [{"id": 1, "content": "a"}, {"id": 2, "content": "b"}],
"total": 3,
"page": 0,
"page_size": 2,
"has_more": True,
},
),
httpx.Response(
200,
json={
"items": [{"id": 3, "content": "c"}],
"total": 3,
"page": 1,
"page_size": 2,
"has_more": False,
},
),
]
)
client = DocumentsClient(http, _BASE)
@ -191,15 +211,17 @@ def _sse_body(events: list[dict]) -> bytes:
@pytest.mark.asyncio
@respx.mock(base_url=_BASE)
async def test_ask_accumulates_text_deltas(respx_mock, http):
body = _sse_body([
{"type": "start", "messageId": "m1"},
{"type": "text-start", "id": "t1"},
{"type": "text-delta", "id": "t1", "delta": "Answer "},
{"type": "text-delta", "id": "t1", "delta": "is "},
{"type": "text-delta", "id": "t1", "delta": "B [citation:42]."},
{"type": "text-end", "id": "t1"},
{"type": "finish"},
])
body = _sse_body(
[
{"type": "start", "messageId": "m1"},
{"type": "text-start", "id": "t1"},
{"type": "text-delta", "id": "t1", "delta": "Answer "},
{"type": "text-delta", "id": "t1", "delta": "is "},
{"type": "text-delta", "id": "t1", "delta": "B [citation:42]."},
{"type": "text-end", "id": "t1"},
{"type": "finish"},
]
)
respx_mock.post("/api/v1/new_chat").mock(
return_value=httpx.Response(
200,
@ -208,9 +230,7 @@ async def test_ask_accumulates_text_deltas(respx_mock, http):
)
)
client = NewChatClient(http, _BASE)
answer = await client.ask(
thread_id=1, search_space_id=2, user_query="What is the answer?"
)
answer = await client.ask(thread_id=1, search_space_id=2, user_query="What is the answer?")
assert answer.text == "Answer is B [citation:42]."
assert answer.finished_normally is True
assert any(c["chunk_id"] == 42 for c in answer.citations)
@ -219,23 +239,21 @@ async def test_ask_accumulates_text_deltas(respx_mock, http):
@pytest.mark.asyncio
@respx.mock(base_url=_BASE)
async def test_ask_409_thread_busy_retries(respx_mock, http):
body = _sse_body([
{"type": "text-delta", "id": "t1", "delta": "ok"},
{"type": "finish"},
])
body = _sse_body(
[
{"type": "text-delta", "id": "t1", "delta": "ok"},
{"type": "finish"},
]
)
busy = httpx.Response(
409,
json={"detail": {"errorCode": "THREAD_BUSY", "message": "busy"}},
headers={"Retry-After": "1"},
)
success = httpx.Response(
200, content=body, headers={"Content-Type": "text/event-stream"}
)
success = httpx.Response(200, content=body, headers={"Content-Type": "text/event-stream"})
respx_mock.post("/api/v1/new_chat").mock(side_effect=[busy, success])
client = NewChatClient(http, _BASE)
answer = await client.ask(
thread_id=1, search_space_id=2, user_query="hi", max_busy_retries=2
)
answer = await client.ask(thread_id=1, search_space_id=2, user_query="hi", max_busy_retries=2)
assert answer.text == "ok"
@ -250,6 +268,4 @@ async def test_ask_409_exhausts_retries(respx_mock, http):
respx_mock.post("/api/v1/new_chat").mock(return_value=busy)
client = NewChatClient(http, _BASE)
with pytest.raises(ThreadBusyError):
await client.ask(
thread_id=1, search_space_id=2, user_query="hi", max_busy_retries=1
)
await client.ask(thread_id=1, search_space_id=2, user_query="hi", max_busy_retries=1)

View file

@ -46,32 +46,24 @@ class TestMerge:
def test_explicit_false_overrides_default_true(self) -> None:
defaults = IngestSettings(use_vision_llm=True)
merged = IngestSettings.merge(
defaults, {"use_vision_llm": False}
)
merged = IngestSettings.merge(defaults, {"use_vision_llm": False})
assert merged.use_vision_llm is False
def test_explicit_true_overrides_default_false(self) -> None:
defaults = IngestSettings(use_vision_llm=False)
merged = IngestSettings.merge(
defaults, {"use_vision_llm": True}
)
merged = IngestSettings.merge(defaults, {"use_vision_llm": True})
assert merged.use_vision_llm is True
def test_none_means_silent(self) -> None:
# Argparse with BooleanOptionalAction yields None when the
# operator passed neither --use-vision-llm nor --no-vision-llm.
defaults = IngestSettings(use_vision_llm=True)
merged = IngestSettings.merge(
defaults, {"use_vision_llm": None}
)
merged = IngestSettings.merge(defaults, {"use_vision_llm": None})
assert merged.use_vision_llm is True
def test_processing_mode_override(self) -> None:
defaults = IngestSettings(processing_mode="basic")
merged = IngestSettings.merge(
defaults, {"processing_mode": "premium"}
)
merged = IngestSettings.merge(defaults, {"processing_mode": "premium"})
assert merged.processing_mode == "premium"
def test_processing_mode_invalid_raises(self) -> None:
@ -134,9 +126,7 @@ class TestAddArgs:
p = argparse.ArgumentParser()
add_ingest_settings_args(
p,
defaults=IngestSettings(
use_vision_llm=False, processing_mode="basic"
),
defaults=IngestSettings(use_vision_llm=False, processing_mode="basic"),
)
return p
@ -158,31 +148,21 @@ class TestAddArgs:
args = parser.parse_args(["--processing-mode", mode])
assert args.processing_mode == mode
def test_processing_mode_rejects_unknown(
self, parser: argparse.ArgumentParser
) -> None:
def test_processing_mode_rejects_unknown(self, parser: argparse.ArgumentParser) -> None:
with pytest.raises(SystemExit):
parser.parse_args(["--processing-mode", "exotic"])
def test_vision_flags_mutually_exclusive(
self, parser: argparse.ArgumentParser
) -> None:
def test_vision_flags_mutually_exclusive(self, parser: argparse.ArgumentParser) -> None:
with pytest.raises(SystemExit):
parser.parse_args(["--use-vision-llm", "--no-vision-llm"])
def test_full_pipeline(self, parser: argparse.ArgumentParser) -> None:
# Operator passes flags + defaults are reasonable. Merge
# should yield exactly what they asked for.
args = parser.parse_args(
["--use-vision-llm", "--processing-mode", "premium"]
)
defaults = IngestSettings(
use_vision_llm=False, processing_mode="basic"
)
args = parser.parse_args(["--use-vision-llm", "--processing-mode", "premium"])
defaults = IngestSettings(use_vision_llm=False, processing_mode="basic")
merged = IngestSettings.merge(defaults, vars(args))
assert merged == IngestSettings(
use_vision_llm=True, processing_mode="premium"
)
assert merged == IngestSettings(use_vision_llm=True, processing_mode="premium")
# ---------------------------------------------------------------------------
@ -240,16 +220,12 @@ class TestHeader:
class TestFormatMd:
def test_full_settings(self) -> None:
out = format_ingest_settings_md(
{"use_vision_llm": True, "processing_mode": "premium"}
)
out = format_ingest_settings_md({"use_vision_llm": True, "processing_mode": "premium"})
assert "vision_llm=`on`" in out
assert "processing_mode=`premium`" in out
def test_default_off(self) -> None:
out = format_ingest_settings_md(
{"use_vision_llm": False, "processing_mode": "basic"}
)
out = format_ingest_settings_md({"use_vision_llm": False, "processing_mode": "basic"})
assert "vision_llm=`off`" in out
assert "processing_mode=`basic`" in out

View file

@ -25,7 +25,12 @@ from surfsense_evals.core.metrics import (
@pytest.mark.parametrize(
"k,n,low,high",
[
(80, 100, 0.7111, 0.8666), # cross-checked vs statsmodels.proportion_confint(method='wilson')
(
80,
100,
0.7111,
0.8666,
), # cross-checked vs statsmodels.proportion_confint(method='wilson')
(50, 100, 0.4038, 0.5962),
(0, 0, 0.0, 1.0),
(0, 10, 0.0, 0.2775),
@ -74,7 +79,7 @@ def test_mcnemar_exact_branch_strong_signal():
assert res.b == 0
assert res.c == 10
assert res.method == "exact"
expected = 2 * (0.5 ** 10)
expected = 2 * (0.5**10)
assert math.isclose(res.p_value, expected, rel_tol=1e-9)

View file

@ -11,7 +11,11 @@ from surfsense_evals.core.parse.answer_letter import AnswerLetterResult
@pytest.mark.parametrize(
"text,expected_letter,expected_strategy",
[
('```json\n{"step_by_step_thinking": "...", "answer_choice": "B"}\n```', "B", "json_envelope"),
(
'```json\n{"step_by_step_thinking": "...", "answer_choice": "B"}\n```',
"B",
"json_envelope",
),
('Reasoning... {"step_by_step_thinking": "x", "answer_choice": "C"}', "C", "json_envelope"),
("Long reasoning.\nAnswer: D", "D", "answer_line"),
("The correct answer is (A).", "A", "answer_line"),

View file

@ -91,7 +91,7 @@ def test_regex_pattern_matches_ts_source():
assert "https?://" in pattern
assert "urlcite" in pattern
assert "doc-" in pattern
assert "\u200B" in pattern
assert "\u200b" in pattern
assert "" in pattern and "" in pattern

View file

@ -44,11 +44,14 @@ class TestExtractFreeformAnswer:
assert extract_freeform_answer("ANSWER: yes") == "yes"
assert extract_freeform_answer("answer: no") == "no"
@pytest.mark.parametrize("text,expected", [
("Answer: 1, 2, 3", "1, 2, 3"),
("Answer: 3.14", "3.14"),
("Answer: spaced ", "spaced"),
])
@pytest.mark.parametrize(
"text,expected",
[
("Answer: 1, 2, 3", "1, 2, 3"),
("Answer: 3.14", "3.14"),
("Answer: spaced ", "spaced"),
],
)
def test_various_payloads(self, text: str, expected: str) -> None:
assert extract_freeform_answer(text) == expected

View file

@ -22,12 +22,16 @@ async def _astream(lines):
@pytest.mark.asyncio
async def test_basic_data_frame():
events = await _alist(
iter_sse_events(_astream([
'data: {"type": "text-delta", "delta": "hi"}',
"",
'data: {"type": "finish"}',
"",
]))
iter_sse_events(
_astream(
[
'data: {"type": "text-delta", "delta": "hi"}',
"",
'data: {"type": "finish"}',
"",
]
)
)
)
assert [e.data for e in events] == [
'{"type": "text-delta", "delta": "hi"}',
@ -38,10 +42,14 @@ async def test_basic_data_frame():
@pytest.mark.asyncio
async def test_done_sentinel_passes_through():
events = await _alist(
iter_sse_events(_astream([
"data: [DONE]",
"",
]))
iter_sse_events(
_astream(
[
"data: [DONE]",
"",
]
)
)
)
assert [e.data for e in events] == ["[DONE]"]
@ -49,11 +57,15 @@ async def test_done_sentinel_passes_through():
@pytest.mark.asyncio
async def test_multiline_data_joins_with_newline():
events = await _alist(
iter_sse_events(_astream([
"data: line1",
"data: line2",
"",
]))
iter_sse_events(
_astream(
[
"data: line1",
"data: line2",
"",
]
)
)
)
assert events[0].data == "line1\nline2"
@ -61,13 +73,17 @@ async def test_multiline_data_joins_with_newline():
@pytest.mark.asyncio
async def test_comments_and_other_fields_ignored():
events = await _alist(
iter_sse_events(_astream([
": heartbeat",
"event: foo",
"id: 123",
"data: payload",
"",
]))
iter_sse_events(
_astream(
[
": heartbeat",
"event: foo",
"id: 123",
"data: payload",
"",
]
)
)
)
assert [e.data for e in events] == ["payload"]
@ -77,8 +93,12 @@ async def test_handles_missing_trailing_blank():
"""Some servers omit the final blank line; the consumer should still emit."""
events = await _alist(
iter_sse_events(_astream([
"data: only-one",
]))
iter_sse_events(
_astream(
[
"data: only-one",
]
)
)
)
assert [e.data for e in events] == ["only-one"]

View file

@ -36,11 +36,18 @@ async def test_payload_shape_matches_openrouter_docs(respx_mock, tiny_pdf: Path)
return httpx.Response(
200,
json={
"choices": [{
"message": {"content": "Answer: B"},
"finish_reason": "stop",
}],
"usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15, "cost": 0.0001},
"choices": [
{
"message": {"content": "Answer: B"},
"finish_reason": "stop",
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 5,
"total_tokens": 15,
"cost": 0.0001,
},
},
)
@ -63,8 +70,7 @@ async def test_payload_shape_matches_openrouter_docs(respx_mock, tiny_pdf: Path)
assert file_part["file"]["filename"] == tiny_pdf.name
assert file_part["file"]["file_data"].startswith("data:application/pdf;base64,")
assert (
base64.b64decode(file_part["file"]["file_data"].split(",", 1)[1])
== tiny_pdf.read_bytes() # noqa: ASYNC240 — test fixture, sync read is fine
base64.b64decode(file_part["file"]["file_data"].split(",", 1)[1]) == tiny_pdf.read_bytes() # noqa: ASYNC240 — test fixture, sync read is fine
)
assert user["content"][1] == {"type": "text", "text": "What is the diagnosis?"}
assert captured["headers"]["authorization"] == "Bearer sk-or-test"
@ -85,22 +91,22 @@ async def test_chat_array_content_concatenates(respx_mock, tiny_pdf: Path):
return_value=httpx.Response(
200,
json={
"choices": [{
"message": {
"content": [
{"type": "text", "text": "Hello "},
{"type": "text", "text": "world"},
{"type": "image_url", "image_url": "ignored"},
]
"choices": [
{
"message": {
"content": [
{"type": "text", "text": "Hello "},
{"type": "text", "text": "world"},
{"type": "image_url", "image_url": "ignored"},
]
}
}
}],
],
"usage": {"prompt_tokens": 1, "completion_tokens": 1},
},
)
)
provider = OpenRouterPdfProvider(
api_key="sk-or-test", base_url=_BASE, model="x/y"
)
provider = OpenRouterPdfProvider(api_key="sk-or-test", base_url=_BASE, model="x/y")
response = await provider.complete(prompt="hi", pdf_path=tiny_pdf)
assert response.text == "Hello world"

View file

@ -65,13 +65,15 @@ class TestParser:
interaction_id="abc",
query="Who directed Inception?",
answer="Christopher Nolan",
pages=[{
"page_name": "Inception (film)",
"page_url": "https://en.wikipedia.org/wiki/Inception",
"page_snippet": "snippet",
"page_result": "<html>full html</html>",
"page_last_modified": "2024-01-01",
}],
pages=[
{
"page_name": "Inception (film)",
"page_url": "https://en.wikipedia.org/wiki/Inception",
"page_snippet": "snippet",
"page_result": "<html>full html</html>",
"page_last_modified": "2024-01-01",
}
],
),
]
path = _make_jsonl_bz2(rows, tmp_path)
@ -120,8 +122,7 @@ class TestParser:
def test_alt_answers_parsed(self, tmp_path: Path) -> None:
rows = [
_row(interaction_id="z", query="q?", answer="42",
alt_ans=["forty-two", "42.0"]),
_row(interaction_id="z", query="q?", answer="42", alt_ans=["forty-two", "42.0"]),
]
path = _make_jsonl_bz2(rows, tmp_path)
parsed = iter_questions(path)
@ -143,22 +144,32 @@ class TestParser:
class TestPageHash:
def test_url_hash_stable(self) -> None:
a = CragPage(
page_name="A", page_url="https://x.test/p?q=1",
page_snippet="", page_html="<html/>",
page_name="A",
page_url="https://x.test/p?q=1",
page_snippet="",
page_html="<html/>",
)
b = CragPage(
page_name="B", page_url="https://x.test/p?q=1",
page_snippet="", page_html="<html/>",
page_name="B",
page_url="https://x.test/p?q=1",
page_snippet="",
page_html="<html/>",
)
assert a.url_hash == b.url_hash
assert len(a.url_hash) == 12
def test_url_hash_unique(self) -> None:
a = CragPage(
page_name="A", page_url="https://x.test/a", page_snippet="", page_html="<html/>",
page_name="A",
page_url="https://x.test/a",
page_snippet="",
page_html="<html/>",
)
b = CragPage(
page_name="B", page_url="https://x.test/b", page_snippet="", page_html="<html/>",
page_name="B",
page_url="https://x.test/b",
page_snippet="",
page_html="<html/>",
)
assert a.url_hash != b.url_hash
@ -174,21 +185,23 @@ class TestStratifiedSample:
(5, "sports", "multi-hop"),
):
for _ in range(n):
out.append(CragQuestion(
qid=f"C{idx:05d}",
interaction_id=f"i{idx}",
query_time="2024-01-01",
query=f"q{idx}?",
gold_answer="a",
alt_answers=[],
domain=domain,
question_type=qtype,
static_or_dynamic="static",
popularity="head",
split=0,
raw_index=idx,
pages=[],
))
out.append(
CragQuestion(
qid=f"C{idx:05d}",
interaction_id=f"i{idx}",
query_time="2024-01-01",
query=f"q{idx}?",
gold_answer="a",
alt_answers=[],
domain=domain,
question_type=qtype,
static_or_dynamic="static",
popularity="head",
split=0,
raw_index=idx,
pages=[],
)
)
idx += 1
return out

View file

@ -152,7 +152,9 @@ class TestGradeDeterministicHappyPath:
class TestGradeDeterministicRefusal:
def test_idk_maps_to_missing(self) -> None:
result = grade_deterministic(
pred="I don't know.", gold="Tim Cook", question_type="simple",
pred="I don't know.",
gold="Tim Cook",
question_type="simple",
)
assert result.grade == "missing"
assert result.score == 0
@ -225,8 +227,11 @@ class TestGradeDeterministicLexicalMiss:
class TestGradeResultShape:
def test_to_dict_round_trip(self) -> None:
result = CragGradeResult(
grade="correct", score=1, method="exact",
normalised_pred="x", normalised_gold="x",
grade="correct",
score=1,
method="exact",
normalised_pred="x",
normalised_gold="x",
)
d = result.to_dict()
assert d["grade"] == "correct"

View file

@ -112,7 +112,9 @@ class TestFallbackStripper:
</body></html>
"""
result = extract_main_content(
html, url="https://x.test/", page_name="Title",
html,
url="https://x.test/",
page_name="Title",
)
assert result.ok
assert "content one" in result.text

View file

@ -63,14 +63,22 @@ class TestCacheFilename:
@pytest.mark.asyncio
@respx.mock
async def test_fetch_success_writes_markdown(tmp_path: Path) -> None:
respx.get(WIKI_API).mock(return_value=httpx.Response(
200,
json={"query": {"pages": [{
"pageid": 1,
"title": "James Buchanan",
"extract": "James Buchanan was the 15th president of the United States.",
}]}},
))
respx.get(WIKI_API).mock(
return_value=httpx.Response(
200,
json={
"query": {
"pages": [
{
"pageid": 1,
"title": "James Buchanan",
"extract": "James Buchanan was the 15th president of the United States.",
}
]
}
},
)
)
fetcher = WikiFetcher(cache_dir=tmp_path, rate_limit_rps=100) # disable throttle
article = await fetcher.fetch("https://en.wikipedia.org/wiki/James_Buchanan")
assert article is not None
@ -83,13 +91,21 @@ async def test_fetch_success_writes_markdown(tmp_path: Path) -> None:
@pytest.mark.asyncio
@respx.mock
async def test_fetch_missing_page_returns_none(tmp_path: Path) -> None:
respx.get(WIKI_API).mock(return_value=httpx.Response(
200,
json={"query": {"pages": [{
"title": "DoesNotExist",
"missing": True,
}]}},
))
respx.get(WIKI_API).mock(
return_value=httpx.Response(
200,
json={
"query": {
"pages": [
{
"title": "DoesNotExist",
"missing": True,
}
]
}
},
)
)
fetcher = WikiFetcher(cache_dir=tmp_path, rate_limit_rps=100)
article = await fetcher.fetch("https://en.wikipedia.org/wiki/DoesNotExist")
assert article is None

View file

@ -99,7 +99,9 @@ class TestListFormat:
assert 0.0 < r.f1 < 1.0
def test_extra_items_lower_precision(self) -> None:
r = grade(pred="apple, banana, cherry, date", gold="apple, banana, cherry", answer_format="List")
r = grade(
pred="apple, banana, cherry, date", gold="apple, banana, cherry", answer_format="List"
)
assert 0.0 < r.f1 < 1.0
# Recall=1, precision=3/4 → F1 ~= 0.857
assert r.f1 == pytest.approx(2 * (3 / 4) * 1 / (3 / 4 + 1), rel=1e-3)