Files
devplacepy/tests/unit/services/nodes/adapters.py
T
2026-10-16 07:20:44 +02:00

92 lines
3.5 KiB
Python

# retoor <retoor@molodetz.nl>
import json
import pytest
from devplacepy.services.nodes.adapters.base import AdapterError, clean, tag_for
from devplacepy.services.nodes.adapters.direct import DevTo, GitHub, HackerNews, Mastodon, StackExchange
from devplacepy.services.nodes.adapters.reddit import Reddit
from devplacepy.services.nodes.adapters.registry import ADAPTERS
from devplacepy.services.nodes.spec import SPEC
def body(value) -> bytes:
return json.dumps(value).encode()
def test_clean_strips_tags_entities_and_caps():
assert clean("<p>a &amp; b</p> c", 50) == "a & b c"
assert len(clean("x" * 900, 100)) == 100
assert clean(None, 10) == ""
def test_tag_for_picks_longest_word():
assert tag_for("sqlite wal mode") == "sqlite"
assert tag_for("!!") == ""
def test_hackernews_parse():
data = {
"hits": [
{"objectID": "7", "title": "T", "points": 5, "num_comments": 2, "author": "a", "created_at": "2026"},
{"objectID": "", "title": "no id"},
"junk",
]
}
hits = HackerNews().parse(body(data), 8)
assert [item.url for item in hits] == ["https://news.ycombinator.com/item?id=7"]
assert hits[0].score == 5 and hits[0].via == "direct"
def test_stackexchange_parse():
data = {"items": [{"title": "Q &amp; A", "link": "https://stackoverflow.com/q/1", "score": 3, "answer_count": 2, "tags": ["c"], "owner": {"display_name": "bob"}}]}
hit = StackExchange().parse(body(data), 8)[0]
assert hit.title == "Q & A" and hit.author == "bob" and hit.comments == 2
def test_github_parse_requires_http_url():
data = {"items": [{"title": "I", "html_url": "javascript:alert(1)"}, {"title": "J", "html_url": "https://github.com/a/b/issues/1", "user": {"login": "u"}}]}
hits = GitHub().parse(body(data), 8)
assert [item.title for item in hits] == ["J"]
def test_devto_and_mastodon_need_a_tag():
with pytest.raises(AdapterError) as error:
DevTo().request("!!", 5)
assert error.value.code == "no_tag"
with pytest.raises(AdapterError):
Mastodon().request("", 5)
assert "tag=sqlite" in DevTo().request("sqlite", 5)
assert "/tag/sqlite" in Mastodon().request("sqlite", 5)
def test_mastodon_parse_strips_html():
data = [{"url": "https://m.example/1", "content": "<p>hello <b>world</b></p>", "account": {"acct": "x@y"}, "favourites_count": 2, "reblogs_count": 1}]
hit = Mastodon().parse(body(data), 5)[0]
assert hit.snippet == "hello world" and hit.score == 3
def test_reddit_parse_and_via():
data = {"data": {"children": [{"data": {"title": "T", "permalink": "/r/x/comments/1/t/", "score": 9, "num_comments": 4, "author": "a", "selftext": "body"}}, {"data": {"title": "bad", "permalink": "http://evil"}}]}}
hits = Reddit().parse(body(data), 5)
assert len(hits) == 1 and hits[0].url == "https://www.reddit.com/r/x/comments/1/t/" and hits[0].via == "fetch"
@pytest.mark.parametrize("adapter", list(ADAPTERS.values()))
def test_forged_structure_is_rejected(adapter):
for forged in (b"not json", body("string"), body({"unexpected": 1}), body(42)):
with pytest.raises(AdapterError):
adapter.parse(forged, 5)
def test_oversize_body_is_rejected():
with pytest.raises(AdapterError) as error:
HackerNews().parse(b" " * (2 * 1024 * 1024 + 1), 5)
assert error.value.code == "body_too_large"
def test_node_adapters_are_exactly_the_spec_adapters():
node_backed = {name for name, adapter in ADAPTERS.items() if not adapter.direct}
assert node_backed == {item.adapter for item in SPEC.destinations}