119 lines
5.5 KiB
Python
119 lines
5.5 KiB
Python
"""Offline regressions for feed metadata, date ranking and challenge filtering."""
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from omni import feeds, rank
|
|
from omni.dates import parse_published
|
|
from omni.model import Item
|
|
from omni.sources import S
|
|
|
|
|
|
@pytest.mark.parametrize("value", [
|
|
"2026-10-20T12:00:00Z", "2026-10-20T15:00:00+03:00",
|
|
"Tue, 20 Oct 2026 12:00:00 GMT", "2026-10-20T12:00:00",
|
|
"1792497600", "1792497600000", 1792497600,
|
|
])
|
|
def test_publication_formats_have_equal_freshness(value, monkeypatch):
|
|
stamp = datetime(2026, 10, 20, 12, tzinfo=timezone.utc).timestamp()
|
|
parsed = parse_published(value)
|
|
assert parsed is not None
|
|
assert parsed.timestamp() == stamp
|
|
monkeypatch.setattr(rank.time, "time", lambda: stamp + 86400)
|
|
assert rank._fresh(value) == pytest.approx(1 - 1 / 30)
|
|
|
|
|
|
def test_date_only_and_invalid_dates(monkeypatch):
|
|
stamp = datetime(2026, 10, 20, tzinfo=timezone.utc).timestamp()
|
|
monkeypatch.setattr(rank.time, "time", lambda: stamp)
|
|
assert rank._fresh("2026-10-20") == 1
|
|
for value in (None, "", "not a date", "99999999999999999999999999", "NaN"):
|
|
assert parse_published(value) is None
|
|
assert rank._fresh(value) == 0.3
|
|
assert rank._fresh("2020-01-01") == 0
|
|
assert parse_published("2026") is None
|
|
assert parse_published("20261020") == datetime(2026, 10, 20, tzinfo=timezone.utc)
|
|
|
|
|
|
def test_atom_alternate_links_author_and_date(monkeypatch):
|
|
src = S("atom_test", "models", "rss", "https://example.invalid/feed.atom")
|
|
xml = '''<feed xmlns="http://www.w3.org/2005/Atom"><entry>
|
|
<title>Runtime release 2.0</title>
|
|
<link rel="self" href="https://example.invalid/api/123"/>
|
|
<link rel="alternate" href="/releases/2.0"/>
|
|
<author><name>Release team</name></author>
|
|
<published>2026-10-20T15:00:00+03:00</published>
|
|
<summary>Faster inference and improved caching.</summary>
|
|
</entry></feed>'''
|
|
monkeypatch.setattr(feeds.net, "get", lambda *a, **kw: SimpleNamespace(status=200, text=xml))
|
|
rows = feeds.fetch(src)
|
|
assert len(rows) == 1
|
|
assert rows[0].url == "https://example.invalid/releases/2.0"
|
|
assert rows[0].author == "Release team"
|
|
assert rows[0].published == "2026-10-20T12:00:00+00:00"
|
|
|
|
|
|
def test_rss_namespaced_creator_and_no_author(monkeypatch):
|
|
src = S("rss_test", "security", "rss", "https://example.invalid/feed")
|
|
xml = '''<rss xmlns:dc="http://purl.org/dc/elements/1.1/"><channel>
|
|
<item><title>Security advisory</title><link>https://example.invalid/advisory</link>
|
|
<dc:creator>Security team</dc:creator><pubDate>Tue, 20 Oct 2026 12:00:00 GMT</pubDate></item>
|
|
<item><title>New patch available</title><link>https://example.invalid/patch</link></item>
|
|
</channel></rss>'''
|
|
monkeypatch.setattr(feeds.net, "get", lambda *a, **kw: SimpleNamespace(status=200, text=xml))
|
|
rows = feeds.fetch(src)
|
|
assert len(rows) == 2
|
|
assert rows[0].author == "Security team"
|
|
assert rows[0].published == "2026-10-20T12:00:00+00:00"
|
|
assert rows[1].author == ""
|
|
|
|
|
|
def test_fetch_drops_invalid_pages_but_keeps_short_real_posts(monkeypatch):
|
|
src = S("test", "security", "rss", "https://example.invalid/feed", "test_adapter")
|
|
def item(path, title, text=""):
|
|
return Item("test", "entry", url=path, title=title, text=text)
|
|
good = item("https://example.invalid/release", "v1.2")
|
|
real = item("https://example.invalid/article", "Access denied", "Real analysis of authorization failure. " * 20)
|
|
rows = [
|
|
item("", "A missing URL"),
|
|
item("javascript:alert(1)", "A dangerous URL"),
|
|
item("https://example.invalid/blank", ""),
|
|
item("https://example.invalid/challenge", "Just a moment...", "Checking your browser"),
|
|
item("https://example.invalid/denied", "Attention Required! | Cloudflare", "captcha"),
|
|
item("https://example.invalid/long-challenge", "Just a moment...",
|
|
"Checking your browser before accessing this site. " * 20),
|
|
{"title": "Malformed adapter result"}, None, "not an Item",
|
|
good, item(good.url, "Duplicate record"), real,
|
|
]
|
|
monkeypatch.setitem(feeds.ADAPTERS, "test_adapter", lambda source, n: rows)
|
|
assert feeds.fetch(src) == [good, real]
|
|
|
|
|
|
def test_fetch_bad_record_does_not_discard_valid_siblings(monkeypatch):
|
|
src = S("test", "security", "rss", "https://example.invalid/feed", "test_adapter")
|
|
good = Item("test", "entry", url="https://example.invalid/release", title="Real release")
|
|
bad = Item("test", "entry", url="https://example.invalid/bad", title="Malformed")
|
|
bad.text = None
|
|
monkeypatch.setitem(feeds.ADAPTERS, "test_adapter", lambda source, n: [bad, good])
|
|
assert feeds.fetch(src) == [good]
|
|
|
|
|
|
@pytest.mark.parametrize("title", ["GPT-5 launched", "Release 2.0", "v2.1.0"])
|
|
def test_feed_keeps_short_release_titles(title):
|
|
src = S("release_test", "models", "atom", "https://example.invalid/feed")
|
|
row = feeds._item(src, "entry", "https://example.invalid/release", title,
|
|
"Long description that must not replace the release title.")
|
|
assert row.title == title
|
|
|
|
|
|
def test_telegram_still_uses_body_for_empty_heading():
|
|
src = S("tg_test", "security", "tg", "https://t.me/s/example", "tg_web")
|
|
body = "An advisory with a meaningful first line."
|
|
row = feeds._item(src, "post", "https://t.me/example/1", "...", body)
|
|
assert row.title == body
|