"""Unit tests for ``openbb_sec.models.rss_litigation``."""
import asyncio
from types import SimpleNamespace
from unittest.mock import AsyncMock, patch
import pytest
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_sec.models import rss_litigation as mod
from openbb_sec.models.rss_litigation import SecRssLitigationFetcher
_RSS = """
SEC v. Example Corp & Others
https://www.sec.gov/litigation/litreleases/2026/lr-26562.htm
The SEC charged Example Corp.Mon, 08 Jun 2026 10:14:13 -0400LR-26562"""
_BODY = (
"
"
"
Full litigation text.
"
)
def test_rss_litigation_empty_feed_raises():
"""aextract_data raises OpenBBError when the feed returns nothing."""
query = SecRssLitigationFetcher.transform_query({})
with patch(
"openbb_core.provider.utils.helpers.amake_request",
AsyncMock(return_value=None),
):
with pytest.raises(OpenBBError):
asyncio.run(SecRssLitigationFetcher.aextract_data(query, None))
def test_rss_litigation_happy_path():
"""The feed is parsed, the full text is fetched, and the model validates."""
def _amake(url, **_):
return _RSS if str(url).endswith("/rss") else _BODY
query = SecRssLitigationFetcher.transform_query({"limit": 1})
with patch(
"openbb_core.provider.utils.helpers.amake_request",
AsyncMock(side_effect=_amake),
):
rows = asyncio.run(SecRssLitigationFetcher.aextract_data(query, None))
assert len(rows) == 1
assert rows[0]["title"] == "SEC v. Example Corp & Others"
assert rows[0]["id"] == "LR-26562"
assert rows[0]["author"] is None
assert "Full litigation text." in rows[0]["body"]
data = SecRssLitigationFetcher.transform_data(query, rows)
assert data[0].title == "SEC v. Example Corp & Others"
assert str(data[0].date).startswith("2026-06-08")
def test_rss_litigation_body_failure_falls_back_to_excerpt():
"""When the release page can't be fetched, body falls back to the excerpt."""
def _amake(url, **_):
if str(url).endswith("/rss"):
return _RSS
raise RuntimeError("boom")
query = SecRssLitigationFetcher.transform_query({"limit": 1})
with patch(
"openbb_core.provider.utils.helpers.amake_request",
AsyncMock(side_effect=_amake),
):
rows = asyncio.run(SecRssLitigationFetcher.aextract_data(query, None))
assert rows[0]["body"] == rows[0]["excerpt"] == "The SEC charged Example Corp."
def test_text_callback_reads_response_text():
"""_text_callback returns the response body text."""
response = SimpleNamespace(text=AsyncMock(return_value="hello"))
assert asyncio.run(mod._text_callback(response, None)) == "hello"
def test_fetch_body_returns_none_when_empty():
"""_fetch_body returns None when the release page has no content."""
with patch(
"openbb_core.provider.utils.helpers.amake_request",
AsyncMock(return_value=None),
):
assert asyncio.run(mod._fetch_body("https://x")) is None
def test_fetch_body_returns_none_without_main_content():
"""_fetch_body returns None when no article/main/body element is present."""
with patch(
"openbb_core.provider.utils.helpers.amake_request",
AsyncMock(return_value="bare text"),
):
assert asyncio.run(mod._fetch_body("https://x")) is None
def test_unparseable_dates_become_none():
"""Missing or malformed pubDate values are coerced to None."""
rss = (
""
"Ahttps://x/lr-1.htm"
"dgarbage"
"Bhttps://x/lr-2.htm"
"d"
""
)
def _amake(url, **_):
return rss if str(url).endswith("/rss") else None
query = SecRssLitigationFetcher.transform_query({"limit": 2})
with patch(
"openbb_core.provider.utils.helpers.amake_request",
AsyncMock(side_effect=_amake),
):
rows = asyncio.run(SecRssLitigationFetcher.aextract_data(query, None))
assert [r["date"] for r in rows] == [None, None]