Files
OpenBB/cli/tests/test_dispatchers_unpack.py
Danglewood 9a30186174 [V5] CLI 2.0 - Pluggable Backends, Non-TTY Default, HTTP Dispatcher, Spec Files + Codegen (#7433)
* stash some changes

* add more robust testing

* mypy

* point PR at V5

* introduce spec file

* codespell

* test fix

* fix workflow environment setup

* fix workflow environment setup

* fix workflow environment setup

* add pyyaml to dependencies

* split lint jobs

* fix workflow environment setup

* fix workflow environment setup

* workflow env setup

* workflow env setup

* clean up code comments

* add auth hook entrypoints

* codespell

* add codegen feature

* codespell

* move _unpack into dispatchers for consistency with codegen packages

* surface nested models in the response

* fix missing coverage in CI

* socrata updates

* test fix

* detect plotly output

* add --include and --exclude flags from generate-extension command

* cap test matrix at python 3.14

* no useless comments

* platform controller command description split

* merge URL overloads from path params

* exclude none and unset from model dump

---------

Co-authored-by: deeleeramone <>
Co-authored-by: Copilot <copilot@github.com>
2026-06-01 19:14:38 +03:00

133 lines
4.2 KiB
Python

"""Tests for openbb_cli.dispatchers._unpack — canonical response unwrapping."""
from __future__ import annotations
import pytest
from openbb_cli.dispatchers._unpack import safe_json_loads, unpack_response
# --- list payloads ---
def test_unpack_response_strips_single_element_list_wrapper():
rows, metadata = unpack_response([{"a": 1}])
assert rows == [{"a": 1}]
assert metadata == {}
def test_unpack_response_recurses_through_single_element_list_of_envelope():
rows, metadata = unpack_response([{"refRates": [{"x": 1}, {"x": 2}]}])
assert rows == [{"x": 1}, {"x": 2}]
assert metadata == {}
def test_unpack_response_keeps_multi_dict_list_as_rows():
rows, metadata = unpack_response([{"a": 1}, {"b": 2}])
assert rows == [{"a": 1}, {"b": 2}]
assert metadata == {}
def test_unpack_response_wraps_array_of_scalars_as_value_rows():
rows, metadata = unpack_response(["2026-01-01", "2026-01-02"])
assert rows == [{"value": "2026-01-01"}, {"value": "2026-01-02"}]
assert metadata == {}
# --- non-dict / scalar payloads ---
def test_unpack_response_wraps_top_level_scalar_as_value_row():
rows, metadata = unpack_response("plain text")
assert rows == [{"value": "plain text"}]
assert metadata == {}
def test_unpack_response_wraps_top_level_int():
rows, metadata = unpack_response(42)
assert rows == [{"value": 42}]
assert metadata == {}
# --- dict payloads ---
def test_unpack_response_strips_single_array_envelope():
rows, metadata = unpack_response(
{"refRates": [{"effectiveDate": "2026-04-30", "type": "TGCR"}]}
)
assert rows == [{"effectiveDate": "2026-04-30", "type": "TGCR"}]
assert metadata == {}
def test_unpack_response_splits_array_from_sibling_metadata():
"""Sibling scalar fields surface as metadata."""
rows, metadata = unpack_response(
{
"asOfDate": "2026-04-30",
"operations": [{"id": 1}, {"id": 2}],
}
)
assert rows == [{"id": 1}, {"id": 2}]
assert metadata == {"asOfDate": "2026-04-30"}
def test_unpack_response_descends_through_single_key_dict_envelope():
rows, metadata = unpack_response(
{"ambs": {"auctions": [{"operationId": "OR1"}, {"operationId": "OR2"}]}}
)
assert rows == [{"operationId": "OR1"}, {"operationId": "OR2"}]
assert metadata == {}
def test_unpack_response_does_not_descend_into_scalar_single_key_value():
"""``{"symbol": "AAPL"}`` is a row dict, not an envelope around ``"AAPL"``."""
rows, metadata = unpack_response({"symbol": "AAPL"})
assert rows == [{"symbol": "AAPL"}]
assert metadata == {}
def test_unpack_response_returns_multi_property_dict_as_single_row():
rows, metadata = unpack_response({"a": 1, "b": 2})
assert rows == [{"a": 1, "b": 2}]
assert metadata == {}
def test_unpack_response_promotes_scalar_array_under_single_key_envelope():
"""Single-key wrapper around an array of strings -> ``{value: x}`` rows."""
rows, metadata = unpack_response({"asOfDates": ["2026-04-30", "2026-04-23"]})
assert rows == [{"value": "2026-04-30"}, {"value": "2026-04-23"}]
assert metadata == {}
def test_unpack_response_returns_empty_when_payload_is_none():
rows, metadata = unpack_response(None)
assert rows == [{"value": None}]
assert metadata == {}
# --- safe_json_loads ---
def test_safe_json_loads_parses_well_formed_payload_directly():
"""Strict ``json.loads`` succeeds on the fast path — no rewrite triggered."""
assert safe_json_loads('{"a": 1, "b": [2, 3]}') == {"a": 1, "b": [2, 3]}
def test_safe_json_loads_substitutes_bare_asterisk_with_null():
"""NY Fed-style ``"foo": *`` sentinel becomes ``null`` so the rest of
the payload still parses instead of failing on the first asterisk."""
text = '{"a": 1, "b": *, "c": [{"d": *}], "e": "kept"}'
assert safe_json_loads(text) == {
"a": 1,
"b": None,
"c": [{"d": None}],
"e": "kept",
}
def test_safe_json_loads_propagates_unfixable_errors():
"""Genuinely malformed JSON (not just bare ``*``) still raises after
the sanitization fallback — the fallback isn't a magical rescue."""
with pytest.raises(ValueError):
safe_json_loads('{"a": ,}')