betty.test_utils.json.linked_data - Betty documentation

Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
Toggle table of contents sidebar

# Source code for betty.test_utils.json.linked_data

    """
    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)

    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)

    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]