"""Tests for basic_memory.schema.resolver -- schema resolution order."""
from unittest.mock import AsyncMock
import pytest
from basic_memory.schema.parser import SchemaDefinition
from basic_memory.schema.resolver import resolve_schema
@pytest.fixture
def mock_search_fn():
"""A mock search function that returns empty by default."""
return AsyncMock(return_value=[])
# --- Inline schema (priority 1) ---
class TestInlineSchema:
@pytest.mark.asyncio
async def test_inline_schema_parsed_directly(self, mock_search_fn):
frontmatter = {
"type": "Meeting",
"schema": {
"date": "string",
"attendees?(array)": "string",
},
}
result = await resolve_schema(frontmatter, mock_search_fn)
assert result is not None
assert isinstance(result, SchemaDefinition)
assert result.entity == "Meeting"
assert len(result.fields) == 2
# Inline schema should NOT call search at all
mock_search_fn.assert_not_called()
@pytest.mark.asyncio
async def test_inline_schema_uses_type_for_entity(self, mock_search_fn):
frontmatter = {
"type": "CustomType",
"schema": {"field": "string"},
}
result = await resolve_schema(frontmatter, mock_search_fn)
assert result.entity == "CustomType"
@pytest.mark.asyncio
async def test_inline_schema_defaults_entity_to_unknown(self, mock_search_fn):
frontmatter = {
"schema": {"field": "string"},
}
result = await resolve_schema(frontmatter, mock_search_fn)
assert result.entity == "unknown"
@pytest.mark.asyncio
async def test_inline_schema_respects_validation_mode(self, mock_search_fn):
frontmatter = {
"type": "Test",
"schema": {"name": "string"},
"settings": {"validation": "strict"},
}
result = await resolve_schema(frontmatter, mock_search_fn)
assert result.validation_mode == "strict"
# --- Explicit reference (priority 2) ---
class TestExplicitReference:
@pytest.mark.asyncio
async def test_explicit_ref_calls_search(self, mock_search_fn):
schema_note_frontmatter = {
"entity": "Person",
"schema": {"name": "string"},
}
mock_search_fn.return_value = [schema_note_frontmatter]
frontmatter = {"schema": "Person"}
result = await resolve_schema(frontmatter, mock_search_fn)
assert result is not None
assert result.entity == "Person"
mock_search_fn.assert_called_once_with("Person")
@pytest.mark.asyncio
async def test_explicit_ref_not_found_falls_through(self, mock_search_fn):
"""If explicit ref search returns nothing, fall through to implicit by type."""
mock_search_fn.return_value = []
frontmatter = {"schema": "NonExistent", "type": "Person"}
await resolve_schema(frontmatter, mock_search_fn)
# Search called twice: once for explicit ref "NonExistent", once for type "Person"
assert mock_search_fn.call_count == 2
@pytest.mark.asyncio
async def test_explicit_ref_not_found_no_type_returns_none(self, mock_search_fn):
frontmatter = {"schema": "NonExistent"}
result = await resolve_schema(frontmatter, mock_search_fn)
assert result is None
# --- Implicit by type (priority 3) ---
class TestImplicitByType:
@pytest.mark.asyncio
async def test_implicit_type_lookup(self, mock_search_fn):
schema_note_frontmatter = {
"entity": "Book",
"schema": {"title": "string"},
}
mock_search_fn.return_value = [schema_note_frontmatter]
frontmatter = {"type": "Book"}
result = await resolve_schema(frontmatter, mock_search_fn)
assert result is not None
assert result.entity == "Book"
mock_search_fn.assert_called_once_with("Book")
@pytest.mark.asyncio
async def test_implicit_type_not_found(self, mock_search_fn):
frontmatter = {"type": "UnknownType"}
result = await resolve_schema(frontmatter, mock_search_fn)
assert result is None
# --- No schema (priority 4) ---
class TestNoSchema:
@pytest.mark.asyncio
async def test_no_schema_no_type(self, mock_search_fn):
result = await resolve_schema({}, mock_search_fn)
assert result is None
mock_search_fn.assert_not_called()
@pytest.mark.asyncio
async def test_empty_frontmatter(self, mock_search_fn):
result = await resolve_schema({}, mock_search_fn)
assert result is None
# --- Priority order ---
class TestResolutionPriority:
@pytest.mark.asyncio
async def test_inline_beats_type(self, mock_search_fn):
"""Inline schema should win over implicit type lookup."""
frontmatter = {
"type": "Person",
"schema": {"custom_field": "string"},
}
result = await resolve_schema(frontmatter, mock_search_fn)
# Inline schema parsed, search never called
assert result is not None
assert result.fields[0].name == "custom_field"
mock_search_fn.assert_not_called()