mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-05 08:51:20 +00:00
* merge branch develop with main * bump desktop version * fix the merge * sync Cargo.lock to desktop 2.0.0 * fix cli conftest.py --------- Co-authored-by: deeleeramone <>
188 lines
6.2 KiB
Python
188 lines
6.2 KiB
Python
"""Tests for SEC investment adviser regulatory documents."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from datetime import date
|
|
from json import dumps
|
|
from typing import cast
|
|
|
|
import pytest
|
|
from openbb_core.app.model.abstract.error import OpenBBError
|
|
from openbb_core.provider.utils.errors import EmptyDataError
|
|
from pydantic import ValidationError
|
|
|
|
from openbb_sec.models import adviser_documents
|
|
from openbb_sec.models.adviser_documents import (
|
|
SecAdviserDocumentsFetcher,
|
|
SecAdviserDocumentsQueryParams,
|
|
)
|
|
|
|
|
|
def _firm_content(**updates: object) -> dict[str, object]:
|
|
content: dict[str, object] = {
|
|
"basicInformation": {
|
|
"firmId": 148826,
|
|
"firmName": "CITADEL ADVISORS LLC",
|
|
"advFilingDate": "06/11/2026",
|
|
"hasPdf": "Y",
|
|
},
|
|
"brochures": {
|
|
"brochuredetails": [
|
|
{
|
|
"brochureVersionID": 1038247,
|
|
"brochureName": "CITADEL ADVISORS LLC ADV BROCHURE",
|
|
"dateSubmitted": "3/31/2026",
|
|
}
|
|
],
|
|
},
|
|
}
|
|
content.update(updates)
|
|
return content
|
|
|
|
|
|
def _firm_payload(content: dict[str, object] | None = None) -> dict[str, object]:
|
|
return {
|
|
"hits": {
|
|
"hits": [
|
|
{
|
|
"_source": {
|
|
"iacontent": dumps(content or _firm_content()),
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize("crd", [None, "", "abc", True, 148826])
|
|
def test_adviser_documents_query_requires_numeric_crd(crd: object) -> None:
|
|
"""Document queries require a numeric CRD string."""
|
|
params = {} if crd is None else {"crd": crd}
|
|
with pytest.raises(ValidationError):
|
|
SecAdviserDocumentsQueryParams.model_validate(params)
|
|
|
|
|
|
def test_adviser_documents_return_one_row_per_document() -> None:
|
|
"""ADV and brochure metadata produce independent flat rows."""
|
|
records = adviser_documents._document_records(_firm_content())
|
|
|
|
assert records == [
|
|
{
|
|
"crd": "148826",
|
|
"firm_name": "CITADEL ADVISORS LLC",
|
|
"document_type": "Form ADV",
|
|
"document_id": None,
|
|
"title": "Form ADV",
|
|
"filing_date": date(2026, 6, 11),
|
|
"url": "https://reports.adviserinfo.sec.gov/reports/ADV/148826/PDF/148826.pdf",
|
|
},
|
|
{
|
|
"crd": "148826",
|
|
"firm_name": "CITADEL ADVISORS LLC",
|
|
"document_type": "Brochure",
|
|
"document_id": "1038247",
|
|
"title": "CITADEL ADVISORS LLC ADV BROCHURE",
|
|
"filing_date": date(2026, 3, 31),
|
|
"url": (
|
|
"https://files.adviserinfo.sec.gov/IAPD/Content/Common/"
|
|
"crd_iapd_Brochure.aspx?BRCHR_VRSN_ID=1038247"
|
|
),
|
|
},
|
|
]
|
|
|
|
|
|
def test_documents_require_source_document_metadata(monkeypatch) -> None:
|
|
"""The documents route fails when IAPD reports no available documents."""
|
|
content = _firm_content(brochures={})
|
|
basic = cast(dict[str, object], content["basicInformation"])
|
|
basic["hasPdf"] = "N"
|
|
|
|
async def fake_get_adviser_firm(*_args: object) -> dict[str, object]:
|
|
return content
|
|
|
|
monkeypatch.setattr(
|
|
adviser_documents,
|
|
"_get_adviser_firm",
|
|
fake_get_adviser_firm,
|
|
)
|
|
query = SecAdviserDocumentsQueryParams(crd="148826")
|
|
|
|
with pytest.raises(EmptyDataError, match="No regulatory documents"):
|
|
asyncio.run(SecAdviserDocumentsFetcher.aextract_data(query, None))
|
|
|
|
|
|
def test_documents_reject_malformed_iapd_response(monkeypatch) -> None:
|
|
"""Missing profile data fails explicitly."""
|
|
|
|
async def fake_request_iapd(*_args: object) -> object:
|
|
return {"hits": {"hits": []}}
|
|
|
|
monkeypatch.setattr(adviser_documents, "request_iapd", fake_request_iapd)
|
|
query = SecAdviserDocumentsQueryParams(crd="148826")
|
|
|
|
with pytest.raises(EmptyDataError, match="No investment adviser firm"):
|
|
asyncio.run(SecAdviserDocumentsFetcher.aextract_data(query, None))
|
|
|
|
|
|
def test_get_adviser_firm_rejects_non_string_iacontent(monkeypatch) -> None:
|
|
async def fake_request_iapd(*_args: object) -> object:
|
|
return {"hits": {"hits": [{"_source": {"iacontent": 12345}}]}}
|
|
|
|
monkeypatch.setattr(adviser_documents, "request_iapd", fake_request_iapd)
|
|
query = SecAdviserDocumentsQueryParams(crd="148826")
|
|
|
|
with pytest.raises(OpenBBError, match="iacontent must be JSON text"):
|
|
asyncio.run(SecAdviserDocumentsFetcher.aextract_data(query, None))
|
|
|
|
|
|
def test_get_adviser_firm_rejects_invalid_json_iacontent(monkeypatch) -> None:
|
|
async def fake_request_iapd(*_args: object) -> object:
|
|
return {"hits": {"hits": [{"_source": {"iacontent": "{bad json"}}]}}
|
|
|
|
monkeypatch.setattr(adviser_documents, "request_iapd", fake_request_iapd)
|
|
query = SecAdviserDocumentsQueryParams(crd="148826")
|
|
|
|
with pytest.raises(OpenBBError, match="invalid JSON"):
|
|
asyncio.run(SecAdviserDocumentsFetcher.aextract_data(query, None))
|
|
|
|
|
|
def test_get_adviser_firm_raises_when_crd_not_matched(monkeypatch) -> None:
|
|
payload = _firm_payload(
|
|
_firm_content(
|
|
basicInformation={
|
|
"firmId": 999999,
|
|
"firmName": "OTHER FIRM",
|
|
"hasPdf": "N",
|
|
}
|
|
)
|
|
)
|
|
|
|
async def fake_request_iapd(*_args: object) -> object:
|
|
return payload
|
|
|
|
monkeypatch.setattr(adviser_documents, "request_iapd", fake_request_iapd)
|
|
query = SecAdviserDocumentsQueryParams(crd="148826")
|
|
|
|
with pytest.raises(EmptyDataError, match="No investment adviser firm"):
|
|
asyncio.run(SecAdviserDocumentsFetcher.aextract_data(query, None))
|
|
|
|
|
|
def test_profile_section_returns_empty_when_not_required() -> None:
|
|
result = adviser_documents._profile_section({}, "missing_section", required=False)
|
|
assert result == {}
|
|
|
|
|
|
def test_object_list_rejects_non_list() -> None:
|
|
with pytest.raises(OpenBBError, match="must be a list"):
|
|
adviser_documents._object_list("not_a_list", "brochuredetails")
|
|
|
|
|
|
def test_yes_no_returns_none_for_absent_flag() -> None:
|
|
assert adviser_documents._yes_no(None) is None
|
|
|
|
|
|
def test_yes_no_rejects_invalid_flag() -> None:
|
|
with pytest.raises(OpenBBError, match="Y/N flag"):
|
|
adviser_documents._yes_no("X")
|