Files
lda-wf/tests/wf_api/test_schema_projection.py
T

196 lines
6.4 KiB
Python

from __future__ import annotations
import pytest
from wf_api.schema_projection import (
project_output_property_to_state_schema,
project_property_to_schema_path,
)
def test_project_output_property_copies_schema_and_defs() -> None:
state_schema = {
"type": "object",
"properties": {"before": {"type": "object"}},
}
output_schema = {
"type": "object",
"properties": {
"after": {"$ref": "#/$defs/Snapshot"},
},
"$defs": {
"Snapshot": {
"type": "object",
"properties": {"clicked": {"type": "boolean"}},
"required": ["clicked"],
}
},
}
projected = project_output_property_to_state_schema(
state_schema=state_schema,
output_schema=output_schema,
output_field="after",
state_field="after",
)
assert projected["properties"]["before"] == {"type": "object"}
assert projected["properties"]["after"] == {"$ref": "#/$defs/Snapshot"}
assert projected["$defs"]["Snapshot"]["properties"]["clicked"] == {
"type": "boolean"
}
assert "after" not in state_schema["properties"]
def test_project_output_property_rejects_missing_output_field() -> None:
with pytest.raises(ValueError, match="output field 'after'"):
project_output_property_to_state_schema(
state_schema={"type": "object", "properties": {}},
output_schema={"type": "object", "properties": {}},
output_field="after",
state_field="after",
)
def test_project_output_property_rejects_conflicting_defs() -> None:
with pytest.raises(ValueError, match=r"conflicting \$defs.Snapshot"):
project_output_property_to_state_schema(
state_schema={
"type": "object",
"properties": {},
"$defs": {"Snapshot": {"type": "string"}},
},
output_schema={
"type": "object",
"properties": {"after": {"$ref": "#/$defs/Snapshot"}},
"$defs": {"Snapshot": {"type": "object"}},
},
output_field="after",
state_field="after",
)
def test_project_output_property_rejects_non_object_state_schema() -> None:
with pytest.raises(ValueError, match="state_schema must be an object schema"):
project_output_property_to_state_schema(
state_schema={"type": "array", "items": {"type": "string"}},
output_schema={
"type": "object",
"properties": {"after": {"type": "object"}},
},
output_field="after",
state_field="after",
)
def test_project_output_property_rejects_existing_state_field() -> None:
with pytest.raises(ValueError, match="state field 'after' already exists"):
project_output_property_to_state_schema(
state_schema={
"type": "object",
"properties": {"after": {"type": "string"}},
},
output_schema={
"type": "object",
"properties": {"after": {"type": "object"}},
},
output_field="after",
state_field="after",
)
def test_project_output_property_allows_equivalent_existing_state_field() -> None:
state_schema = {
"type": "object",
"properties": {"after": {"type": "object"}},
}
projected = project_output_property_to_state_schema(
state_schema=state_schema,
output_schema={
"type": "object",
"properties": {"after": {"type": "object"}},
},
output_field="after",
state_field="after",
allow_existing_equivalent=True,
)
assert projected == state_schema
def test_project_output_property_rejects_invalid_output_schema() -> None:
with pytest.raises(ValueError, match="output_schema is not valid JSON Schema"):
project_output_property_to_state_schema(
state_schema={"type": "object", "properties": {}},
output_schema={
"type": "object",
"properties": {"after": {"type": "definitely-not-jsonschema"}},
},
output_field="after",
state_field="after",
)
def test_project_schema_property_inserts_nested_path_and_defs() -> None:
projected = project_property_to_schema_path(
target_schema={"type": "object", "properties": {}},
source_schema={
"type": "object",
"properties": {"after": {"$ref": "#/$defs/Snapshot"}},
"$defs": {
"Snapshot": {
"type": "object",
"properties": {"clicked": {"type": "boolean"}},
}
},
},
source_field="after",
target_parts=("session", "after"),
)
assert projected["properties"]["session"]["type"] == "object"
assert projected["properties"]["session"]["properties"]["after"] == {
"$ref": "#/$defs/Snapshot"
}
assert projected["$defs"]["Snapshot"]["properties"]["clicked"] == {
"type": "boolean"
}
def test_project_schema_property_rejects_existing_nested_target() -> None:
with pytest.raises(ValueError, match="schema path 'session.after' already exists"):
project_property_to_schema_path(
target_schema={
"type": "object",
"properties": {
"session": {
"type": "object",
"properties": {"after": {"type": "string"}},
}
},
},
source_schema={
"type": "object",
"properties": {"after": {"type": "object"}},
},
source_field="after",
target_parts=("session", "after"),
)
def test_project_schema_property_rejects_non_object_ancestor() -> None:
with pytest.raises(ValueError, match="schema path 'session' is not an object"):
project_property_to_schema_path(
target_schema={
"type": "object",
"properties": {"session": {"type": "string"}},
},
source_schema={
"type": "object",
"properties": {"after": {"type": "object"}},
},
source_field="after",
target_parts=("session", "after"),
)