"""RED tests for scripts.twitter_threads.fetch_thread (normalize URL + HTML to ThreadData)."""
from __future__ import annotations
from pathlib import Path
import pytest
from scripts.twitter_threads.error_types import PostData, ThreadData
from scripts.twitter_threads.fetch_thread import fetch_thread_from_html, _normalize_url
def test_normalize_url_strips_query() -> None:
assert _normalize_url("https://x.com/VPCOMPRESSB/status/1991383117571957052?s=20") == "https://x.com/VPCOMPRESSB/status/1991383117571957052"
assert _normalize_url("https://x.com/NOTimothyLottes/status/123") == "https://x.com/NOTimothyLottes/status/123"
def test_parse_html_single_post(tmp_path: Path) -> None:
html = """
Hello thread
"""
p = tmp_path / "x.html"
p.write_text(html, encoding="utf-8")
result = fetch_thread_from_html(p)
assert result.is_ok
td = result.data
assert td is not None
assert td.root_post_id == "1757198624818168210"
assert len(td.posts) == 1
post = td.posts[0]
assert post.post_id == "1757198624818168210"
assert post.author == "Timothy Lottes"
assert post.handle == "NOTimothyLottes"
assert post.timestamp == "2024-02-13T12:00:00Z"
assert "Hello thread" in post.text
assert post.media_urls == ("https://pbs.twimg.com/media/AAA.jpg",)
assert post.metrics.reply_count == 5
assert post.metrics.repost_count == 10
assert post.metrics.like_count == 100
assert post.metrics.view_count == 5000
assert td.source_url == "https://x.com/NOTimothyLottes/status/1757198624818168210"
def test_parse_html_thread(tmp_path: Path) -> None:
html = """
p1
p2
p3
"""
p = tmp_path / "x.html"
p.write_text(html, encoding="utf-8")
result = fetch_thread_from_html(p)
assert result.is_ok
td = result.data
assert td is not None
assert len(td.posts) == 3
ids = [pt.post_id for pt in td.posts]
assert ids == ["1", "2", "3"]
assert td.posts[1].reply_to_id == "1"
assert td.posts[2].reply_to_id == "2"
assert td.posts[0].reply_to_id is None
def test_parse_html_no_media(tmp_path: Path) -> None:
html = """
no media here
"""
p = tmp_path / "x.html"
p.write_text(html, encoding="utf-8")
result = fetch_thread_from_html(p)
assert result.is_ok
td = result.data
assert td is not None
assert td.posts[0].media_urls == ()
def test_parse_html_quote(tmp_path: Path) -> None:
html = """
quoting
"""
p = tmp_path / "x.html"
p.write_text(html, encoding="utf-8")
result = fetch_thread_from_html(p)
assert result.is_ok
td = result.data
assert td is not None
assert td.posts[0].quote_of_id == "999"
def test_parse_html_malformed(tmp_path: Path) -> None:
html = "no articles here
"
p = tmp_path / "x.html"
p.write_text(html, encoding="utf-8")
result = fetch_thread_from_html(p)
assert result.is_ok is False
assert result.error is not None
assert result.error.kind == "ParseError"
def test_parse_html_view_count_absent(tmp_path: Path) -> None:
html = """
x
"""
p = tmp_path / "x.html"
p.write_text(html, encoding="utf-8")
result = fetch_thread_from_html(p)
assert result.is_ok
td = result.data
assert td is not None
assert td.posts[0].metrics.view_count is None
assert td.posts[0].metrics.reply_count == 0