Files
p-mosein 2987d93ba7 pydivkit. migrate to uv+hatch, decompose entities.py, fix Python 3.14 compat
commit_hash:cf5070a543788fa57136adb1a4a7ea42f4490329
2026-02-10 16:34:59 +03:00

259 lines
9.0 KiB
Python

"""Tests for pydivkit.core.schema module.
Covers _field_to_schema and its helper functions directly.
"""
from __future__ import annotations
import enum
from typing import Any, Mapping, Optional, Sequence, Union
import pytest
from pydivkit.core import BaseEntity, Expr, Field
from pydivkit.core.fields import _Field
from pydivkit.core.schema import (
BUILTIN_TYPES_TO_SCHEMA,
SchemaType,
_add_field_extra_to_schema,
_dict_to_schema,
_enum_to_schema,
_field_to_schema,
_list_to_schema,
_type_field_to_schema,
_union_to_schema,
)
# ---------------------------------------------------------------------------
# _type_field_to_schema
# ---------------------------------------------------------------------------
class TestTypeFieldToSchema:
def test_produces_enum_schema(self) -> None:
field = _Field(name="type", default="my_component")
result = _type_field_to_schema(field)
assert result == {
"type": "string",
"enum": ["my_component"],
}
# ---------------------------------------------------------------------------
# _enum_to_schema
# ---------------------------------------------------------------------------
class TestEnumToSchema:
def test_multiple_values(self) -> None:
class Direction(enum.Enum):
LEFT = "left"
RIGHT = "right"
UP = "up"
result = _enum_to_schema(Direction)
assert result["type"] == "string"
assert set(result["enum"]) == {"left", "right", "up"}
def test_single_value(self) -> None:
class Single(enum.Enum):
ONLY = "only"
result = _enum_to_schema(Single)
assert result == {"type": "string", "enum": ["only"]}
# ---------------------------------------------------------------------------
# _list_to_schema
# ---------------------------------------------------------------------------
class TestListToSchema:
def test_sequence_of_int(self) -> None:
defs: dict[str, SchemaType] = {}
result = _list_to_schema(Sequence[int], defs, {})
assert result == {
"type": "array",
"items": {"type": "integer"},
}
def test_sequence_of_str(self) -> None:
defs: dict[str, SchemaType] = {}
result = _list_to_schema(Sequence[str], defs, {})
assert result == {
"type": "array",
"items": {"type": "string"},
}
# ---------------------------------------------------------------------------
# _dict_to_schema
# ---------------------------------------------------------------------------
class TestDictToSchema:
def test_returns_object_schema(self) -> None:
result = _dict_to_schema()
assert result == {
"type": "object",
"additionalProperties": True,
}
# ---------------------------------------------------------------------------
# _union_to_schema
# ---------------------------------------------------------------------------
class TestUnionToSchema:
def test_anyof_generation(self) -> None:
defs: dict[str, SchemaType] = {}
result = _union_to_schema(None, Union[int, str], {}, defs)
assert "anyOf" in result
type_values = [a["type"] for a in result["anyOf"]]
assert "integer" in type_values
assert "string" in type_values
def test_multi_type_union(self) -> None:
"""Union[int, str, float] produces anyOf with all arms."""
defs: dict[str, SchemaType] = {}
result = _union_to_schema(None, Union[int, str, float], {}, defs)
assert "anyOf" in result
assert len(result["anyOf"]) == 3
# ---------------------------------------------------------------------------
# _add_field_extra_to_schema
# ---------------------------------------------------------------------------
class TestAddFieldExtraToSchema:
def test_description_added(self) -> None:
field = _Field(description="A field")
schema: SchemaType = {"type": "string"}
_add_field_extra_to_schema(field, schema)
assert schema["description"] == "A field"
def test_default_added(self) -> None:
field = _Field(default="val")
schema: SchemaType = {"type": "string"}
_add_field_extra_to_schema(field, schema)
assert schema["default"] == "val"
def test_constraints_added(self) -> None:
field = _Field(constraints={"minimum": 0, "maximum": 100})
schema: SchemaType = {"type": "integer"}
_add_field_extra_to_schema(field, schema)
assert schema["minimum"] == 0
assert schema["maximum"] == 100
def test_empty_field_no_extras(self) -> None:
field = _Field()
schema: SchemaType = {"type": "integer"}
_add_field_extra_to_schema(field, schema)
assert schema == {"type": "integer"}
# ---------------------------------------------------------------------------
# _field_to_schema (main dispatcher)
# ---------------------------------------------------------------------------
class TestFieldToSchema:
def test_type_field_dispatch(self) -> None:
field = _Field(name="type", default="comp")
defs: dict[str, SchemaType] = {}
result = _field_to_schema(field, str, {}, defs)
assert result["type"] == "string"
assert result["enum"] == ["comp"]
def test_builtin_int(self) -> None:
defs: dict[str, SchemaType] = {}
result = _field_to_schema(None, int, {}, defs)
assert result == {"type": "integer"}
def test_builtin_str(self) -> None:
defs: dict[str, SchemaType] = {}
result = _field_to_schema(None, str, {}, defs)
assert result == {"type": "string"}
def test_builtin_float(self) -> None:
defs: dict[str, SchemaType] = {}
result = _field_to_schema(None, float, {}, defs)
assert result == {"type": "number"}
def test_builtin_bool(self) -> None:
defs: dict[str, SchemaType] = {}
result = _field_to_schema(None, bool, {}, defs)
assert result["type"] == "integer"
assert result["format"] == "boolean"
def test_expr_type(self) -> None:
defs: dict[str, SchemaType] = {}
result = _field_to_schema(None, Expr, {}, defs)
assert result["type"] == "string"
assert "pattern" in result
def test_any_type(self) -> None:
defs: dict[str, SchemaType] = {}
result = _field_to_schema(None, Any, {}, defs)
assert result == {}
def test_sequence_type(self) -> None:
defs: dict[str, SchemaType] = {}
result = _field_to_schema(None, Sequence[int], {}, defs)
assert result["type"] == "array"
assert result["items"]["type"] == "integer"
def test_mapping_type(self) -> None:
defs: dict[str, SchemaType] = {}
result = _field_to_schema(None, Mapping[str, Any], {}, defs)
assert result["type"] == "object"
def test_union_type(self) -> None:
defs: dict[str, SchemaType] = {}
result = _field_to_schema(None, Union[int, str], {}, defs)
assert "anyOf" in result
def test_entity_type(self) -> None:
class Nested(BaseEntity):
x: int = Field(default=0)
defs: dict[str, SchemaType] = {}
result = _field_to_schema(None, Nested, {}, defs)
assert "$ref" in result
assert "Nested" in result["$ref"]
def test_enum_type(self) -> None:
class Color(enum.Enum):
RED = "red"
GREEN = "green"
defs: dict[str, SchemaType] = {}
result = _field_to_schema(None, Color, {}, defs)
assert result["type"] == "string"
assert set(result["enum"]) == {"red", "green"}
def test_unknown_type_raises(self) -> None:
defs: dict[str, SchemaType] = {}
with pytest.raises(TypeError, match="unknown type"):
_field_to_schema(None, object, {}, defs)
def test_optional_unwraps(self) -> None:
"""Optional[int] unwraps to int schema."""
defs: dict[str, SchemaType] = {}
result = _field_to_schema(None, Optional[int], {}, defs)
assert result == {"type": "integer"}
def test_field_extras_applied(self) -> None:
field = _Field(
description="count",
constraints={"minimum": 0},
)
defs: dict[str, SchemaType] = {}
result = _field_to_schema(field, int, {}, defs)
assert result["type"] == "integer"
assert result["description"] == "count"
assert result["minimum"] == 0
# ---------------------------------------------------------------------------
# BUILTIN_TYPES_TO_SCHEMA coverage
# ---------------------------------------------------------------------------
class TestBuiltinTypesToSchema:
def test_all_expected_types_present(self) -> None:
expected = {int, float, bool, str, bytes, Expr, Any}
assert set(BUILTIN_TYPES_TO_SCHEMA.keys()) == expected
def test_immutable(self) -> None:
"""BUILTIN_TYPES_TO_SCHEMA should be immutable."""
with pytest.raises(TypeError):
BUILTIN_TYPES_TO_SCHEMA[int] = {} # type: ignore[index]