"""Tests for basic_memory.schema.parser -- Picoschema parsing."""
import pytest
from basic_memory.schema.parser import (
SchemaDefinition,
parse_picoschema,
parse_schema_note,
_parse_field_key,
_parse_type_and_description,
_is_entity_ref_type,
SCALAR_TYPES,
)
# --- _parse_field_key ---
class TestParseFieldKey:
def test_simple_required(self):
name, required, is_array, is_enum, is_object = _parse_field_key("name")
assert name == "name"
assert required is True
assert is_array is False
assert is_enum is False
assert is_object is False
def test_optional(self):
name, required, is_array, is_enum, is_object = _parse_field_key("role?")
assert name == "role"
assert required is False
def test_array(self):
name, required, is_array, is_enum, is_object = _parse_field_key("tags(array)")
assert name == "tags"
assert required is True
assert is_array is True
def test_optional_array(self):
name, required, is_array, is_enum, is_object = _parse_field_key("tags?(array)")
assert name == "tags"
assert required is False
assert is_array is True
def test_enum(self):
name, required, is_array, is_enum, is_object = _parse_field_key("status?(enum)")
assert name == "status"
assert required is False
assert is_enum is True
def test_object(self):
name, required, is_array, is_enum, is_object = _parse_field_key("metadata?(object)")
assert name == "metadata"
assert required is False
assert is_object is True
def test_required_enum(self):
name, required, is_array, is_enum, is_object = _parse_field_key("status(enum)")
assert name == "status"
assert required is True
assert is_enum is True
# --- _parse_type_and_description ---
class TestParseTypeAndDescription:
def test_type_only(self):
type_str, desc = _parse_type_and_description("string")
assert type_str == "string"
assert desc is None
def test_type_with_description(self):
type_str, desc = _parse_type_and_description("string, full name")
assert type_str == "string"
assert desc == "full name"
def test_entity_ref_with_description(self):
type_str, desc = _parse_type_and_description("Organization, employer")
assert type_str == "Organization"
assert desc == "employer"
def test_whitespace_handling(self):
type_str, desc = _parse_type_and_description(" string , a description ")
assert type_str == "string"
assert desc == "a description"
# --- _is_entity_ref_type ---
class TestIsEntityRefType:
def test_scalar_types_not_entity_ref(self):
for scalar in SCALAR_TYPES:
assert _is_entity_ref_type(scalar) is False
def test_capitalized_is_entity_ref(self):
assert _is_entity_ref_type("Organization") is True
assert _is_entity_ref_type("Person") is True
def test_lowercase_not_entity_ref(self):
assert _is_entity_ref_type("custom") is False
def test_empty_string(self):
assert _is_entity_ref_type("") is False
# --- parse_picoschema ---
class TestParsePicoschema:
def test_required_string_field(self):
fields = parse_picoschema({"name": "string"})
assert len(fields) == 1
assert fields[0].name == "name"
assert fields[0].type == "string"
assert fields[0].required is True
def test_optional_field(self):
fields = parse_picoschema({"role?": "string"})
assert fields[0].name == "role"
assert fields[0].required is False
def test_field_with_description(self):
fields = parse_picoschema({"name": "string, full name"})
assert fields[0].description == "full name"
def test_array_field(self):
fields = parse_picoschema({"tags?(array)": "string"})
assert fields[0].name == "tags"
assert fields[0].is_array is True
assert fields[0].required is False
def test_entity_ref_field(self):
fields = parse_picoschema({"works_at?": "Organization, employer"})
assert fields[0].name == "works_at"
assert fields[0].type == "Organization"
assert fields[0].is_entity_ref is True
assert fields[0].description == "employer"
def test_enum_field_with_list(self):
fields = parse_picoschema({"status?(enum)": ["active", "inactive"]})
assert fields[0].name == "status"
assert fields[0].is_enum is True
assert fields[0].enum_values == ["active", "inactive"]
def test_enum_field_with_string(self):
fields = parse_picoschema({"status?(enum)": "active"})
assert fields[0].is_enum is True
assert fields[0].enum_values == ["active"]
def test_enum_values_coerced_to_string(self):
fields = parse_picoschema({"year?(enum)": [2020, 2021, 2022]})
assert fields[0].enum_values == ["2020", "2021", "2022"]
def test_object_field(self):
fields = parse_picoschema(
{
"address?(object)": {
"street": "string",
"city": "string",
}
}
)
assert fields[0].name == "address"
assert fields[0].type == "object"
assert len(fields[0].children) == 2
assert fields[0].children[0].name == "street"
assert fields[0].children[1].name == "city"
def test_dict_value_treated_as_object(self):
"""A dict value without explicit (object) is still treated as an object."""
fields = parse_picoschema(
{
"metadata": {
"source": "string",
}
}
)
assert fields[0].type == "object"
assert len(fields[0].children) == 1
def test_multiple_fields(self):
fields = parse_picoschema(
{
"name": "string",
"role?": "string",
"works_at?": "Organization",
}
)
assert len(fields) == 3
names = [f.name for f in fields]
assert "name" in names
assert "role" in names
assert "works_at" in names
# --- parse_schema_note ---
class TestParseSchemaNote:
def test_basic_schema_note(self):
frontmatter = {
"type": "schema",
"entity": "Person",
"version": 2,
"schema": {
"name": "string",
"role?": "string",
},
}
result = parse_schema_note(frontmatter)
assert isinstance(result, SchemaDefinition)
assert result.entity == "Person"
assert result.version == 2
assert len(result.fields) == 2
assert result.validation_mode == "warn"
def test_default_version(self):
frontmatter = {
"entity": "Person",
"schema": {"name": "string"},
}
result = parse_schema_note(frontmatter)
assert result.version == 1
def test_strict_validation_mode(self):
frontmatter = {
"entity": "Person",
"schema": {"name": "string"},
"settings": {"validation": "strict"},
}
result = parse_schema_note(frontmatter)
assert result.validation_mode == "strict"
def test_missing_entity_raises(self):
with pytest.raises(ValueError, match="entity"):
parse_schema_note({"schema": {"name": "string"}})
def test_missing_schema_dict_raises(self):
with pytest.raises(ValueError, match="schema"):
parse_schema_note({"entity": "Person"})
def test_schema_not_dict_raises(self):
with pytest.raises(ValueError, match="schema"):
parse_schema_note({"entity": "Person", "schema": "not-a-dict"})
def test_non_dict_settings_defaults_to_warn(self):
frontmatter = {
"entity": "Person",
"schema": {"name": "string"},
"settings": "invalid",
}
result = parse_schema_note(frontmatter)
assert result.validation_mode == "warn"