Files
pyflowx/tests/test_json.py
T

127 lines
4.0 KiB
Python

"""_json 抽象层测试。
验证 orjson/stdlib 双后端的输出语义一致。由于 orjson 是可选依赖,
本测试在两种环境下均应通过(orjson 已安装或未安装)。
"""
from __future__ import annotations
import io
from pathlib import Path
import pytest
from pyflowx._json import JSONDecodeError, dump, dumps, load, loads
class TestDumpsLoads:
"""dumps/loads 往返测试。"""
def test_dumps_basic_dict(self) -> None:
s = dumps({"a": 1, "b": [1, 2, 3]})
assert loads(s) == {"a": 1, "b": [1, 2, 3]}
def test_dumps_returns_str(self) -> None:
s = dumps({"x": 1})
assert isinstance(s, str)
def test_dumps_none(self) -> None:
assert dumps(None) == "null"
def test_dumps_bool(self) -> None:
assert dumps(True) == "true"
assert dumps(False) == "false"
def test_dumps_int(self) -> None:
assert dumps(42) == "42"
def test_dumps_float(self) -> None:
s = dumps(3.14)
assert loads(s) == 3.14
def test_dumps_unicode(self) -> None:
"""ensure_ascii=False:中文字符不应被转义为 \\uXXXX。"""
s = dumps({"msg": "条件不满足"})
assert "条件不满足" in s
assert "\\u" not in s
def test_dumps_indent(self) -> None:
"""indent=2 应产生多行格式。"""
s = dumps({"a": 1}, indent=2)
assert "\n" in s
def test_dumps_indent_none_compact(self) -> None:
"""indent=None 应产生紧凑单行。"""
s = dumps({"a": 1}, indent=None)
assert "\n" not in s
def test_dumps_default_callable(self) -> None:
"""default 参数应处理不可序列化的类型。"""
class Custom:
def __str__(self) -> str:
return "custom-value"
s = dumps({"obj": Custom()}, default=str)
assert loads(s) == {"obj": "custom-value"}
def test_dumps_non_serialisable_raises(self) -> None:
"""不可序列化的值(无 default)应抛 TypeError。"""
with pytest.raises(TypeError):
_ = dumps(object())
def test_loads_bytes(self) -> None:
"""loads 应接受 bytes 输入。"""
assert loads(b'{"x": 1}') == {"x": 1}
def test_loads_str(self) -> None:
"""loads 应接受 str 输入。"""
assert loads('{"x": 1}') == {"x": 1}
def test_loads_invalid_raises(self) -> None:
with pytest.raises(JSONDecodeError):
_ = loads("not valid json")
class TestDumpLoad:
"""dump/load 文件句柄往返测试。"""
def test_dump_load_round_trip(self, tmp_path: Path) -> None:
path = tmp_path / "test.json"
data = {"a": 1, "b": [1, 2, 3], "c": "中文"}
with open(path, "w", encoding="utf-8") as fh:
dump(data, fh, ensure_ascii=False, indent=2)
with open(path, encoding="utf-8") as fh:
assert load(fh) == data
def test_dump_indent(self, tmp_path: Path) -> None:
path = tmp_path / "test.json"
with open(path, "w", encoding="utf-8") as fh:
dump({"a": 1}, fh, indent=2)
text = path.read_text(encoding="utf-8")
assert "\n" in text
def test_dump_compact(self, tmp_path: Path) -> None:
path = tmp_path / "test.json"
with open(path, "w", encoding="utf-8") as fh:
dump({"a": 1}, fh, indent=None)
text = path.read_text(encoding="utf-8")
assert "\n" not in text
def test_dump_default_callable(self, tmp_path: Path) -> None:
path = tmp_path / "test.json"
with open(path, "w", encoding="utf-8") as fh:
dump({"p": Path("/tmp/x")}, fh, default=str)
with open(path, encoding="utf-8") as fh:
assert load(fh) == {"p": "/tmp/x"}
class TestStringIO:
"""通过 StringIO 验证 dump/load 与 dumps/loads 一致。"""
def test_dump_load_stringio(self) -> None:
buf = io.StringIO()
dump({"a": 1, "b": [1, 2]}, buf, ensure_ascii=False)
buf.seek(0)
assert load(buf) == {"a": 1, "b": [1, 2]}