127 lines
4.0 KiB
Python
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]}
|