"""
Test utilities for :py:mod:`betty.json.linked_data`.
"""
from __future__ import annotations
from collections.abc import Mapping, Sequence, Callable, Awaitable
from typing import TYPE_CHECKING
from typing_extensions import TypeVar
from betty.app import App
from betty.project import Project
from betty.serde.dump import Dump
if TYPE_CHECKING:
from betty.json.schema import Schema
from betty.json.linked_data import LinkedDataDumpable, LinkedDataDumpableProvider
_T = TypeVar("_T")
_DumpT = TypeVar("_DumpT", bound=Dump, default=Dump)
[docs]
async def assert_dumps_linked_data(sut: LinkedDataDumpable[Schema, _DumpT]) -> _DumpT:
"""
Dump an object's linked data and assert it is valid.
"""
return await _assert_linked_data_dump(sut.linked_data_schema, sut.dump_linked_data)
[docs]
async def assert_dumps_linked_data_for(
sut: LinkedDataDumpableProvider[_T, Schema, _DumpT], target: _T
) -> _DumpT:
"""
Dump an object's linked data and assert it is valid.
"""
async def _dump(project: Project) -> _DumpT:
return await sut.dump_linked_data_for(project, target)
return await _assert_linked_data_dump(sut.linked_data_schema_for, _dump)
async def _assert_linked_data_dump(
schema: Callable[[Project], Awaitable[Schema]],
dump: Callable[[Project], Awaitable[_DumpT]],
) -> _DumpT:
async with (
App.new_temporary() as app,
app,
Project.new_temporary(app) as project,
project,
):
actual = await dump(project)
# Validate the raw dump.
sut_schema = await schema(project)
sut_schema.validate(actual)
# Normalize the dump after validation (so we are assured it is absolutely valid),
# but before returning, so calling code can use simpler comparisons.
return _normalize(actual)
def _normalize(dump: _DumpT) -> _DumpT:
if isinstance(dump, Mapping):
return { # type: ignore[return-value]
key: _normalize(value)
for key, value in dump.items()
if not key.startswith("$")
}
if isinstance(dump, Sequence) and not isinstance(dump, str):
return list(map(_normalize, dump)) # type: ignore[return-value]
return dump # type: ignore[return-value]