forked from retoor/devplacepy
92 lines
3.5 KiB
Python
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 & 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 & 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}
|