# Copyright 2013 Camptocamp SA
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)

from unittest import mock

from odoo.addons.component.core import Component, WorkContext
from odoo.addons.component.tests.common import TransactionComponentRegistryCase
from odoo.addons.connector.components.mapper import (
    MapOptions,
    MappingDefinition,
    changed_by,
    convert,
    external_to_m2o,
    follow_m2o_relations,
    m2o_to_external,
    mapping,
    none,
    only_create,
)


class TestMapper(TransactionComponentRegistryCase):
    def setUp(self):
        super().setUp()
        self._setup_registry(self)
        self.comp_registry.load_components("connector")

    def test_mapping_decorator(self):
        class KifKrokerMapper(Component):
            _name = "kif.kroker.mapper"
            _inherit = "base.mapper"

            @changed_by("name", "city")
            @mapping
            @only_create
            def name(self):
                pass

            @changed_by("email")
            @mapping
            def email(self):
                pass

            @changed_by("street")
            @mapping
            def street(self):
                pass

            def no_decorator(self):
                pass

        # build our mapper component
        KifKrokerMapper._build_component(self.comp_registry)

        # what mappings we expect
        name_def = MappingDefinition(changed_by={"name", "city"}, only_create=True)
        email_def = MappingDefinition(changed_by={"email"}, only_create=False)
        street_def = MappingDefinition(changed_by={"street"}, only_create=False)

        # get our component by name in the components registry
        comp = self.comp_registry["kif.kroker.mapper"]
        # _map_methods contains the aggregated mapping methods for a Mapper
        self.assertEqual(
            comp._map_methods,
            {"name": name_def, "email": email_def, "street": street_def},
        )

    def test_mapping_decorator_cross_classes(self):
        """Mappings should not propagate to other classes"""

        class MomMapper(Component):
            _name = "mom.mapper"
            _inherit = "base.mapper"
            _apply_on = "res.users"

            @changed_by("name", "city")
            @mapping
            def name(self):
                pass

        class ZappMapper(Component):
            _name = "zapp.mapper"
            _inherit = "base.mapper"
            _apply_on = "res.users"

            @changed_by("email")
            @only_create
            @mapping
            def email(self):
                pass

        self._build_components(MomMapper, ZappMapper)

        mom_def = MappingDefinition(changed_by={"name", "city"}, only_create=False)
        zapp_def = MappingDefinition(changed_by={"email"}, only_create=True)

        comp = self.comp_registry["mom.mapper"]
        self.assertEqual(comp._map_methods, {"name": mom_def})
        comp = self.comp_registry["zapp.mapper"]
        self.assertEqual(comp._map_methods, {"email": zapp_def})

    def test_mapping_decorator_cumul(self):
        """Mappings should cumulate the ``super`` mappings
        and the local mappings."""

        class FryMapper(Component):
            _name = "fry.mapper"
            _inherit = "base.mapper"
            _apply_on = "res.users"

            @changed_by("name", "city")
            @mapping
            def name(self):
                pass

        # pylint: disable=R7980
        class FryMapperInherit(Component):
            _inherit = "fry.mapper"

            @changed_by("email")
            @mapping
            def email(self):
                pass

        self._build_components(FryMapper, FryMapperInherit)

        name_def = MappingDefinition(changed_by={"name", "city"}, only_create=False)
        email_def = MappingDefinition(changed_by={"email"}, only_create=False)

        comp = self.comp_registry["fry.mapper"]
        self.assertEqual(comp._map_methods, {"name": name_def, "email": email_def})

    def test_mapping_decorator_cumul_changed_by(self):
        """Mappings should cumulate the changed_by fields of the
        ``super`` mappings and the local mappings"""

        class FryMapper(Component):
            _name = "fry.mapper"
            _inherit = "base.mapper"
            _apply_on = "res.users"

            @changed_by("name", "city")
            @mapping
            def name(self):
                pass

        class FryMapperInherit(Component):
            _inherit = (  # pylint: disable=consider-merging-classes-inherited
                "fry.mapper"
            )
            _apply_on = "res.users"

            @changed_by("email")
            @mapping
            def name(self):
                pass

        class ThirdMapper(Component):
            _name = "third.mapper"
            _inherit = "fry.mapper"
            _apply_on = "res.users"

            @changed_by("email", "street")
            @mapping
            def name(self):
                pass

        self._build_components(FryMapper, FryMapperInherit, ThirdMapper)

        name_def = MappingDefinition(
            changed_by={"name", "city", "email"}, only_create=False
        )

        comp = self.comp_registry["fry.mapper"]
        self.assertEqual(comp._map_methods, {"name": name_def})

        name_def = MappingDefinition(
            changed_by={"name", "city", "email", "street"}, only_create=False
        )
        comp = self.comp_registry["third.mapper"]
        self.assertEqual(comp._map_methods, {"name": name_def})

    def test_several_bases_cumul(self):
        class FryMapper(Component):
            _name = "fry.mapper"
            _inherit = "base.mapper"
            _apply_on = "res.users"

            @changed_by("name", "city")
            @mapping
            def name(self):
                pass

            @only_create
            @mapping
            def street(self):
                pass

            @only_create
            @mapping
            def zip(self):
                pass

        class FarnsworthMapper(Component):
            _name = "farnsworth.mapper"
            _inherit = "base.mapper"
            _apply_on = "res.users"

            @changed_by("email")
            @mapping
            def name(self):
                pass

            @changed_by("street")
            @mapping
            def city(self):
                pass

            @mapping
            def zip(self):
                pass

        class ThirdMapper(Component):
            _name = "third.mapper"
            _inherit = ["fry.mapper", "farnsworth.mapper"]
            _apply_on = "res.users"

            @changed_by("email", "street")
            @mapping
            def name(self):
                pass

            @mapping
            def email(self):
                pass

        self._build_components(FryMapper, FarnsworthMapper, ThirdMapper)

        name_def = MappingDefinition(
            changed_by={"name", "city", "email", "street"}, only_create=False
        )
        street_def = MappingDefinition(changed_by=set(), only_create=True)
        city_def = MappingDefinition(changed_by={"street"}, only_create=False)
        email_def = MappingDefinition(changed_by=set(), only_create=False)
        zip_def = MappingDefinition(changed_by=set(), only_create=True)

        comp = self.comp_registry["third.mapper"]
        self.assertEqual(comp._map_methods["name"], name_def)
        self.assertEqual(comp._map_methods["street"], street_def)
        self.assertEqual(comp._map_methods["city"], city_def)
        self.assertEqual(comp._map_methods["email"], email_def)
        self.assertEqual(comp._map_methods["zip"], zip_def)

    def test_mapping_record(self):
        """Map a record and check the result"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"
            _apply_on = "res.users"

            direct = [("name", "out_name")]

            @mapping
            def street(self, record):
                return {"out_street": record["street"].upper()}

        self._build_components(MyMapper)

        record = {"name": "Guewen", "street": "street"}
        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)
        map_record = mapper.map_record(record)
        expected = {"out_name": "Guewen", "out_street": "STREET"}
        self.assertEqual(map_record.values(), expected)
        self.assertEqual(map_record.values(for_create=True), expected)

    def test_mapping_record_on_create(self):
        """Map a record and check the result for creation of record"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"
            _apply_on = "res.users"

            direct = [("name", "out_name")]

            @mapping
            def street(self, record):
                return {"out_street": record["street"].upper()}

            @only_create
            @mapping
            def city(self, record):
                return {"out_city": "city"}

        self._build_components(MyMapper)

        record = {"name": "Guewen", "street": "street"}
        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)
        map_record = mapper.map_record(record)
        expected = {"out_name": "Guewen", "out_street": "STREET"}
        self.assertEqual(map_record.values(), expected)
        expected = {"out_name": "Guewen", "out_street": "STREET", "out_city": "city"}
        self.assertEqual(map_record.values(for_create=True), expected)

    def test_mapping_update(self):
        """Force values on a map record"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"

            direct = [("name", "out_name")]

            @mapping
            def street(self, record):
                return {"out_street": record["street"].upper()}

            @only_create
            @mapping
            def city(self, record):
                return {"out_city": "city"}

        self._build_components(MyMapper)

        record = {"name": "Guewen", "street": "street"}
        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)
        map_record = mapper.map_record(record)
        map_record.update({"test": 1}, out_city="forced")
        expected = {
            "out_name": "Guewen",
            "out_street": "STREET",
            "out_city": "forced",
            "test": 1,
        }
        self.assertEqual(map_record.values(), expected)
        expected = {
            "out_name": "Guewen",
            "out_street": "STREET",
            "out_city": "forced",
            "test": 1,
        }
        self.assertEqual(map_record.values(for_create=True), expected)

    # pylint: disable=W8110
    def test_finalize(self):
        """Inherit finalize to modify values"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"

            direct = [("name", "out_name")]

            def finalize(self, record, values):
                result = super().finalize(record, values)
                result["test"] = "abc"
                return result

        self._build_components(MyMapper)

        record = {"name": "Guewen", "street": "street"}
        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)
        map_record = mapper.map_record(record)
        expected = {"out_name": "Guewen", "test": "abc"}
        self.assertEqual(map_record.values(), expected)
        expected = {"out_name": "Guewen", "test": "abc"}
        self.assertEqual(map_record.values(for_create=True), expected)

    def test_some_fields(self):
        """Map only a selection of fields"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"

            direct = [("name", "out_name"), ("street", "out_street")]

            @changed_by("country")
            @mapping
            def country(self, record):
                return {"country": "country"}

        self._build_components(MyMapper)

        record = {"name": "Guewen", "street": "street", "country": "country"}
        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)
        map_record = mapper.map_record(record)
        expected = {"out_name": "Guewen", "country": "country"}
        self.assertEqual(map_record.values(fields=["name", "country"]), expected)
        expected = {"out_name": "Guewen", "country": "country"}
        self.assertEqual(
            map_record.values(for_create=True, fields=["name", "country"]), expected
        )

    def test_mapping_modifier(self):
        """Map a direct record with a modifier function"""

        def do_nothing(field):
            def transform(self, record, to_attr):
                return record[field]

            return transform

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"

            direct = [(do_nothing("name"), "out_name")]

        self._build_components(MyMapper)

        record = {"name": "Guewen"}
        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)
        map_record = mapper.map_record(record)
        expected = {"out_name": "Guewen"}
        self.assertEqual(map_record.values(), expected)
        self.assertEqual(map_record.values(for_create=True), expected)

    def test_mapping_direct_property(self):
        """Map a direct record with 'direct' being a property"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"

            @property
            def direct(self):
                return [("name", "out_name")]

        self._build_components(MyMapper)

        record = {"name": "Foo"}
        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)
        map_record = mapper.map_record(record)
        expected = {"out_name": "Foo"}
        self.assertEqual(map_record.values(), expected)
        self.assertEqual(map_record.values(for_create=True), expected)

    def test_mapping_convert(self):
        """Map a direct record with the convert modifier function"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"

            direct = [(convert("name", int), "out_name")]

        self._build_components(MyMapper)

        record = {"name": "300"}
        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)
        map_record = mapper.map_record(record)
        expected = {"out_name": 300}
        self.assertEqual(map_record.values(), expected)
        self.assertEqual(map_record.values(for_create=True), expected)

    def test_mapping_modifier_none(self):
        """Pipeline of modifiers"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"

            direct = [(none("in_f"), "out_f"), (none("in_t"), "out_t")]

        self._build_components(MyMapper)

        record = {"in_f": False, "in_t": True}
        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)
        map_record = mapper.map_record(record)
        expected = {"out_f": None, "out_t": True}
        self.assertEqual(map_record.values(), expected)
        self.assertEqual(map_record.values(for_create=True), expected)

    def test_mapping_modifier_pipeline(self):
        """Pipeline of modifiers"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"

            direct = [
                (none(convert("in_f", bool)), "out_f"),
                (none(convert("in_t", bool)), "out_t"),
            ]

        self._build_components(MyMapper)

        record = {"in_f": 0, "in_t": 1}
        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)
        map_record = mapper.map_record(record)
        expected = {"out_f": None, "out_t": True}
        self.assertEqual(map_record.values(), expected)
        self.assertEqual(map_record.values(for_create=True), expected)

    def test_modifier_import_filter_field(self):
        """A direct mapping with a modifier must still be considered
        from the list of fields
        """

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"

            direct = [
                ("field", "field2"),
                ("no_field", "no_field2"),
                (convert("name", int), "out_name"),
            ]

        self._build_components(MyMapper)

        record = {"name": "300", "field": "value", "no_field": "no_value"}
        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)
        map_record = mapper.map_record(record)
        expected = {"out_name": 300, "field2": "value"}
        self.assertEqual(map_record.values(fields=["field", "name"]), expected)
        self.assertEqual(
            map_record.values(for_create=True, fields=["field", "name"]), expected
        )

    def test_modifier_export_filter_field(self):
        """A direct mapping with a modifier on an export mapping"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.export.mapper"

            direct = [
                ("field", "field2"),
                ("no_field", "no_field2"),
                (convert("name", int), "out_name"),
            ]

        self._build_components(MyMapper)

        record = {"name": "300", "field": "value", "no_field": "no_value"}
        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)
        map_record = mapper.map_record(record)
        expected = {"out_name": 300, "field2": "value"}
        self.assertEqual(map_record.values(fields=["field", "name"]), expected)
        self.assertEqual(
            map_record.values(for_create=True, fields=["field", "name"]), expected
        )

    def test_mapping_custom_option(self):
        """Usage of custom options in mappings"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"

            @mapping
            def any(self, record):
                if self.options.custom:
                    res = True
                else:
                    res = False
                return {"res": res}

        self._build_components(MyMapper)

        record = {}
        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)
        map_record = mapper.map_record(record)
        expected = {"res": True}
        self.assertEqual(map_record.values(custom=True), expected)

    def test_mapping_custom_option_not_defined(self):
        """Usage of custom options not defined raise AttributeError"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"

            @mapping
            def any(self, record):
                if self.options.custom is None:
                    res = True
                else:
                    res = False
                return {"res": res}

        self._build_components(MyMapper)

        record = {}
        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)
        map_record = mapper.map_record(record)
        expected = {"res": True}
        self.assertEqual(map_record.values(), expected)

    def test_map_options(self):
        """Test MapOptions"""
        options = MapOptions({"xyz": "abc"}, k=1)
        options.l = 2  # noqa: E741
        self.assertEqual(options["xyz"], "abc")
        self.assertEqual(options["k"], 1)
        self.assertEqual(options["l"], 2)
        self.assertEqual(options.xyz, "abc")
        self.assertEqual(options.k, 1)
        self.assertEqual(options.l, 2)
        self.assertEqual(options["undefined"], None)
        self.assertEqual(options.undefined, None)

    def test_changed_by_fields(self):
        """Test attribute ``_changed_by_fields`` on Mapper."""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.export.mapper"

            direct = [
                ("street", "out_street"),
                (none("in_t"), "out_t"),
                (none(convert("in_f", bool)), "out_f"),
            ]

            @changed_by("name", "city")
            @mapping
            def name(self):
                pass

            @changed_by("email")
            @mapping
            def email(self):
                pass

            def no_decorator(self):
                pass

        self._build_components(MyMapper)

        work = mock.MagicMock(name="WorkContext()")
        mapper = self.comp_registry["my.mapper"](work)

        self.assertEqual(
            mapper.changed_by_fields(),
            {"street", "in_t", "in_f", "name", "city", "email"},
        )


class TestMapperRecordsets(TransactionComponentRegistryCase):
    """Test mapper with "real" records instead of mocks"""

    def setUp(self):
        super().setUp()
        self._setup_registry(self)
        self.comp_registry.load_components("connector")

        backend_record = mock.Mock()
        backend_record.env = self.env
        self.work = WorkContext(
            model_name="res.partner",
            collection=backend_record,
            components_registry=self.comp_registry,
        )

    def test_mapping_modifier_follow_m2o_relations(self):
        """Map with the follow_m2o_relations modifier"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"

            direct = [(follow_m2o_relations("parent_id.name"), "parent_name")]

        self._build_components(MyMapper)

        partner = self.env.ref("base.res_partner_address_4")
        mapper = self.comp_registry["my.mapper"](self.work)
        map_record = mapper.map_record(partner)
        expected = {"parent_name": "Deco Addict"}
        self.assertEqual(map_record.values(), expected)
        self.assertEqual(map_record.values(for_create=True), expected)


class TestMapperBinding(TransactionComponentRegistryCase):
    """Test Mapper with Bindings"""

    def setUp(self):
        super().setUp()
        self._setup_registry(self)
        self.comp_registry.load_components("connector")

        backend_record = mock.Mock()
        backend_record.env = self.env
        backend_record._name = "my.collection"
        self.work = WorkContext(
            model_name="res.partner",
            collection=backend_record,
            components_registry=self.comp_registry,
        )

        self.country_binder = mock.MagicMock(name="country_binder")
        self.country_binder.return_value = self.country_binder
        self.country_binder._name = "test.binder"
        self.country_binder._inherit = "base.binder"
        self.country_binder.apply_on_models = ["res.country"]
        self.country_binder._usage = "binder"
        self.country_binder._collection = "my.collection"
        self.country_binder._abstract = False
        self.comp_registry["test.binder"] = self.country_binder

    def test_mapping_m2o_to_external(self):
        """Map a direct record with the m2o_to_external modifier function"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"
            _apply_on = "res.partner"

            direct = [(m2o_to_external("country_id"), "country")]

        self._build_components(MyMapper)

        partner = self.env.ref("base.main_partner")
        partner.write({"country_id": self.env.ref("base.ch").id})
        self.country_binder.to_external.return_value = 10

        mapper = self.comp_registry["my.mapper"](self.work)
        map_record = mapper.map_record(partner)
        self.assertEqual(map_record.values(), {"country": 10})
        self.country_binder.to_external.assert_called_once_with(
            partner.country_id.id, wrap=False
        )

    def test_mapping_backend_to_m2o(self):
        """Map a direct record with the backend_to_m2o modifier function"""

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"
            _apply_on = "res.partner"

            direct = [(external_to_m2o("country"), "country_id")]

        self._build_components(MyMapper)

        record = {"country": 10}
        ch = self.env.ref("base.ch")
        self.country_binder.to_internal.return_value = ch
        mapper = self.comp_registry["my.mapper"](self.work)
        map_record = mapper.map_record(record)
        self.assertEqual(map_record.values(), {"country_id": ch.id})
        self.country_binder.to_internal.assert_called_once_with(10, unwrap=False)

    def test_mapping_record_children_no_map_child(self):
        """Map a record with children, using default MapChild"""
        # we need these components which make the 'link' between
        # the main mapper and the line mapper

        class LineMapper(Component):
            _name = "line.mapper"
            _inherit = "base.import.mapper"
            _apply_on = "res.currency.rate"

            direct = [("name", "name")]

            @mapping
            def price(self, record):
                return {"rate": record["rate"] * 2}

            @only_create
            @mapping
            def discount(self, record):
                return {"test": 0.5}

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"
            _apply_on = "res.currency"

            direct = [("name", "name")]

            children = [("lines", "line_ids", "res.currency.rate")]

        self._build_components(LineMapper, MyMapper)

        record = {
            "name": "SO1",
            "lines": [
                {"name": "2013-11-07", "rate": 10},
                {"name": "2013-11-08", "rate": 20},
            ],
        }
        mapper = self.comp_registry["my.mapper"](self.work)
        map_record = mapper.map_record(record)
        expected = {
            "name": "SO1",
            "line_ids": [
                (0, 0, {"name": "2013-11-07", "rate": 20}),
                (0, 0, {"name": "2013-11-08", "rate": 40}),
            ],
        }
        self.assertEqual(map_record.values(), expected)
        expected = {
            "name": "SO1",
            "line_ids": [
                (0, 0, {"name": "2013-11-07", "rate": 20, "test": 0.5}),
                (0, 0, {"name": "2013-11-08", "rate": 40, "test": 0.5}),
            ],
        }
        self.assertEqual(map_record.values(for_create=True), expected)

    def test_mapping_record_children(self):
        """Map a record with children, using defined MapChild"""
        # we need these components which make the 'link' between
        # the main mapper and the line mapper

        class LineMapper(Component):
            _name = "line.mapper"
            _inherit = "base.import.mapper"
            _apply_on = "res.currency.rate"

            direct = [("name", "name")]

            @mapping
            def price(self, record):
                return {"rate": record["rate"] * 2}

            @only_create
            @mapping
            def discount(self, record):
                return {"test": 0.5}

        class LineImportMapChild(Component):
            _name = "line.map.child.import"
            _inherit = "base.map.child.import"
            _apply_on = "res.currency.rate"

            def format_items(self, items_values):
                return [("ABC", values) for values in items_values]

        class MyMapper(Component):
            _name = "my.mapper"
            _inherit = "base.import.mapper"
            _apply_on = "res.currency"

            direct = [("name", "name")]

            children = [("lines", "line_ids", "res.currency.rate")]

        self._build_components(LineMapper, LineImportMapChild, MyMapper)

        record = {
            "name": "SO1",
            "lines": [
                {"name": "2013-11-07", "rate": 10},
                {"name": "2013-11-08", "rate": 20},
            ],
        }
        mapper = self.comp_registry["my.mapper"](self.work)
        map_record = mapper.map_record(record)
        expected = {
            "name": "SO1",
            "line_ids": [
                ("ABC", {"name": "2013-11-07", "rate": 20}),
                ("ABC", {"name": "2013-11-08", "rate": 40}),
            ],
        }
        self.assertEqual(map_record.values(), expected)
        expected = {
            "name": "SO1",
            "line_ids": [
                ("ABC", {"name": "2013-11-07", "rate": 20, "test": 0.5}),
                ("ABC", {"name": "2013-11-08", "rate": 40, "test": 0.5}),
            ],
        }
        self.assertEqual(map_record.values(for_create=True), expected)

    def test_mapping_record_children_void(self):
        """Map a record with children, using defined MapChild"""

        class LineMapper(Component):
            _name = "line.mapper"
            _inherit = "base.import.mapper"
            _apply_on = "res.currency.rate"

            @mapping
            def price(self, record):
                rate = record.get("rate")
                if rate and rate < 40:
                    return {"rate": record["rate"] * 2}

        class SaleLineImportMapChild(Component):
            _name = "sale.line.mapper"
            _inherit = "base.map.child.import"
            _apply_on = "res.currency.rate"

            def format_items(self, items_values):
                return [("ABC", values) for values in items_values]

        class ObjectMapper(Component):
            _name = "currency.mapper"
            _inherit = "base.import.mapper"
            _apply_on = "res.currency"

            direct = [("name", "name")]

            children = [("lines", "line_ids", "res.currency.rate")]

        self._build_components(ObjectMapper, SaleLineImportMapChild, LineMapper)

        # Test with an excluded child record
        record = {
            "name": "SO1",
            "lines": [{"rate": 10}, {"rate": 20}, {"rate": 30}, {"rate": 40}],
        }
        mapper = self.comp_registry["currency.mapper"](self.work)
        map_record = mapper.map_record(record)

        expected = {
            "name": "SO1",
            "line_ids": [
                ("ABC", {"rate": 20}),
                ("ABC", {"rate": 40}),
                ("ABC", {"rate": 60}),
            ],
        }
        self.assertEqual(map_record.values(), expected)
