Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: adapt dictionaries before comparing in roundtrip unit-tests #58

Merged
merged 2 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 65 additions & 3 deletions tests/convert/test_convert_eip712_round_trip.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,81 @@
import json
from pathlib import Path
from typing import Any

import pytest
from eip712 import EIP712DAppDescriptor

from erc7730.common.pydantic import model_from_json_file_with_includes
from erc7730.common.pydantic import model_from_json_file_with_includes, model_to_json_str
from erc7730.convert.convert import convert_and_print_errors
from erc7730.convert.convert_eip712_to_erc7730 import EIP712toERC7730Converter
from erc7730.convert.convert_erc7730_input_to_resolved import ERC7730InputToResolved
from erc7730.convert.convert_erc7730_to_eip712 import ERC7730toEIP712Converter
from erc7730.model.display import FieldFormat
from erc7730.model.input.descriptor import InputERC7730Descriptor
from tests.assertions import assert_model_json_equals
from tests.assertions import assert_dict_equals, assert_model_json_equals
from tests.cases import path_id
from tests.dict_utils import del_by_path, is_in_path, map_by_path
from tests.files import ERC7730_EIP712_DESCRIPTORS, LEGACY_EIP712_DESCRIPTORS


def _adapt_and_comapre_erc7730(input: InputERC7730Descriptor, output: InputERC7730Descriptor) -> None:
input_dict, output_dict = json.loads(model_to_json_str(input)), json.loads(model_to_json_str(output))

# $schema is not present in EIP-712
del_by_path(input_dict, "$schema")

# Addresses may be in EIP-55 format, convert to lower case in input
def _lowercase_addresses(deployments: list[dict[str, Any]]) -> Any:
for deployment in deployments:
if "address" in deployment:
deployment["address"] = deployment["address"].lower()
return deployments

map_by_path(
input_dict,
_lowercase_addresses,
"context",
"eip712",
"deployments",
)

# chainId and verifyingContract are always generated by the converter
# even when not explicitly present in the input
for key in "chainId", "verifyingContract":
path = "context", "eip712", "domain", key
if not is_in_path(input_dict, *path):
del_by_path(output_dict, *path)

# Some metadata cannot be stored in EIP712, and thus are lost with the roundtrip
del_by_path(input_dict, "metadata", "info")
del_by_path(input_dict, "metadata", "token")
del_by_path(input_dict, "metadata", "token")
del_by_path(input_dict, "context", "eip712", "domain", "version")

# Adapt formats objects to match EIP-712 conversion
def _cleanup_formats(formats: dict[str, Any]) -> Any:
for _, message in formats.items():
# Remove ERC-7730 specific fields
del_by_path(message, "$id")
del_by_path(message, "required")
del_by_path(message, "screens")
if "fields" in message:
for field in message["fields"]:
# Other formats are always converted to RAW
if "format" in field and field["format"] not in (
FieldFormat.AMOUNT,
FieldFormat.TOKEN_AMOUNT,
FieldFormat.DATE,
):
field["format"] = "raw"

return formats

map_by_path(input_dict, _cleanup_formats, "display", "formats")

assert_dict_equals(input_dict, output_dict)


@pytest.mark.parametrize("input_file", ERC7730_EIP712_DESCRIPTORS, ids=path_id)
def test_roundtrip_from_erc7730(input_file: Path) -> None:
input_erc7730_descriptor = InputERC7730Descriptor.load(input_file)
Expand All @@ -29,7 +91,7 @@ def test_roundtrip_from_erc7730(input_file: Path) -> None:
assert output_erc7730_descriptor is not None
if isinstance(output_erc7730_descriptor, dict):
pytest.skip("Multiple descriptors tests not supported")
assert_model_json_equals(input_erc7730_descriptor, output_erc7730_descriptor)
_adapt_and_comapre_erc7730(input_erc7730_descriptor, output_erc7730_descriptor)


@pytest.mark.parametrize("input_file", LEGACY_EIP712_DESCRIPTORS, ids=path_id)
Expand Down
33 changes: 33 additions & 0 deletions tests/dict_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import contextlib
import operator
from collections.abc import Callable
from functools import reduce
from typing import Any


def get_by_path(root: dict[str, Any], *paths: str) -> Any:
"""Access a nested object in root by item sequence."""
return reduce(operator.getitem, list(paths), root)


def is_in_path(root: dict[str, Any], *paths: str) -> bool:
"""Check if a key-value in a nested object in root by item sequence exists."""
try:
get_by_path(root, *paths)
return True
except KeyError:
return False


def del_by_path(root: dict[str, Any], *paths: str) -> None:
"""Delete a key-value in a nested object in root by item sequence."""
path_list = list(paths)
with contextlib.suppress(KeyError):
del get_by_path(root, *path_list[:-1])[path_list[-1]]


def map_by_path(root: dict[str, Any], func: Callable[[Any], Any], *paths: str) -> None:
"""Map a function to a key-value in a nested object in root by item sequence."""
path_list = list(paths)
with contextlib.suppress(KeyError):
get_by_path(root, *path_list[:-1])[path_list[-1]] = func(get_by_path(root, *paths))
Loading