Skip to content

Commit

Permalink
l10n_br_mdfe_spec: monkey patch to run tests
Browse files Browse the repository at this point in the history
use to 2 monkey patches to be able to run the XML import tests
against AbstractModel MDFe models
  • Loading branch information
rvalyi committed Jan 16, 2025
1 parent 7a9cf7f commit c465cbc
Showing 1 changed file with 136 additions and 4 deletions.
140 changes: 136 additions & 4 deletions l10n_br_mdfe_spec/tests/test_mdfe_import.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Copyright 2020 Akretion - Raphael Valyi <raphael.valyi@akretion.com>
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).
# flake8: noqa: C901

import re
from datetime import datetime
Expand All @@ -8,8 +9,11 @@
import pkg_resources
from nfelib.mdfe.bindings.v3_0.mdfe_v3_00 import Tmdfe

from odoo import api
from odoo.tests import SavepointCase
from odoo import api, fields, models
from odoo.fields import Command
from odoo.models import BaseModel, NewId
from odoo.tests import TransactionCase
from odoo.tools import OrderedSet

from ..models import spec_mixin

Expand Down Expand Up @@ -103,15 +107,143 @@ def build_attrs_fake(self, node, create_m2o=False):

@api.model
def match_or_create_m2o_fake(self, comodel, new_value, create_m2o=False):
return comodel.new(new_value).id
return comodel.new(new_value)._ids[0]


spec_mixin.MdfeSpecMixin.build_fake = build_fake
spec_mixin.MdfeSpecMixin.build_attrs_fake = build_attrs_fake
spec_mixin.MdfeSpecMixin.match_or_create_m2o_fake = match_or_create_m2o_fake


class MdfeImportTest(SavepointCase):
# in version 12, 13 and 14, the code above would properly allow loading NFe XMLs
# as an Odoo AbstractModel structure for minimal testing of these structures.
# However in version Odoo 15 and 16 (at least), the ORM has trouble when
# doing env["some.model"].new(vals) if some.model is an AbstractModel like the
# models in this module. This is strange as new is available for AbstractModel...
# Anyway, only 2 methods are problematic for what we want to test here so
# a workaround is to monkey patch them as done in the next lines.
# Note that we only want test loading the XML data structure here,
# we remove the monkey patch after the tests and even if it's a dirty
# workaround it doesn't matter much because in the more completes tests in l10n_br_nfe
# we the models are made concrete so this problem does not occur anymore.
def fields_convert_to_cache(self, value, record, validate=True):
"""
A monkey patched version of convert_to_cache that works with
new instances of AbstractModel. Look at the lines after
# THE NEXT LINE WAS PATCHED:
and # THE NEXT 4 LINES WERE PATCHED:
to see the change.
"""
# cache format: tuple(ids)
if isinstance(value, BaseModel):
if validate and value._name != self.comodel_name:
raise ValueError("Wrong value for %s: %s" % (self, value))
ids = value._ids
if record and not record.id:
# x2many field value of new record is new records
ids = tuple(it and NewId(it) for it in ids)
return ids
elif isinstance(value, (list, tuple)):
# value is a list/tuple of commands, dicts or record ids
comodel = record.env[self.comodel_name]
# if record is new, the field's value is new records
# THE NEXT LINE WAS PATCHED:
if record and hasattr(record, "id") and not record.id:
browse = lambda it: comodel.browse([it and NewId(it)])
else:
browse = comodel.browse
# determine the value ids
ids = OrderedSet(record[self.name]._ids if validate else ())
# modify ids with the commands
for command in value:
if isinstance(command, (tuple, list)):
if command[0] == Command.CREATE:
# THE NEXT 4 LINES WERE PATCHED:
if hasattr(comodel.new(command[2], ref=command[1]), "id"):
ids.add(comodel.new(command[2], ref=command[1]).id)
else:
ids.add(comodel.new(command[2], ref=command[1])._ids[0])
elif command[0] == Command.UPDATE:
line = browse(command[1])
if validate:
line.update(command[2])
else:
line._update_cache(command[2], validate=False)
ids.add(line.id)
elif command[0] in (Command.DELETE, Command.UNLINK):
ids.discard(browse(command[1]).id)
elif command[0] == Command.LINK:
ids.add(browse(command[1]).id)
elif command[0] == Command.CLEAR:
ids.clear()
elif command[0] == Command.SET:
ids = OrderedSet(browse(it).id for it in command[2])
elif isinstance(command, dict):
ids.add(comodel.new(command).id)
else:
ids.add(browse(command).id)
# return result as a tuple
return tuple(ids)
elif not value:
return ()
raise ValueError("Wrong value for %s: %s" % (self, value))


fields_convert_to_cache._original_method = fields._RelationalMulti.convert_to_cache
fields._RelationalMulti.convert_to_cache = fields_convert_to_cache


def models_update_cache(self, values, validate=True):
"""
A monkey patched version of _update_cache that works with
new instances of AbstractModel. Look at the lines after
# THE NEXT LINE WAS PATCHED:
to see the change.
"""
self.ensure_one()
cache = self.env.cache
fields = self._fields
try:
field_values = [(fields[name], value) for name, value in values.items()]
except KeyError as e:
raise ValueError("Invalid field %r on model %r" % (e.args[0], self._name))
# convert monetary fields after other columns for correct value rounding
for field, value in sorted(field_values, key=lambda item: item[0].write_sequence):
cache.set(self, field, field.convert_to_cache(value, self, validate))
# set inverse fields on new records in the comodel
if field.relational:
# THE NEXT LINE WAS PATCHED:
inv_recs = self[field.name].filtered(
lambda r: hasattr(r, "id") and not r.id
)
if not inv_recs:
continue
for invf in self.pool.field_inverses[field]:
# DLE P98: `test_40_new_fields`
# /home/dle/src/odoo/master-nochange-fp/odoo/addons/test_new_api/tests/test_new_fields.py
# Be careful to not break `test_onchange_taxes_1`, `test_onchange_taxes_2`, `test_onchange_taxes_3`
# If you attempt to find a better solution
for inv_rec in inv_recs:
if not cache.contains(inv_rec, invf):
val = invf.convert_to_cache(self, inv_rec, validate=False)
cache.set(inv_rec, invf, val)
else:
invf._update(inv_rec, self)


models_update_cache._original_method = models.BaseModel._update_cache
models.BaseModel._update_cache = models_update_cache


class NFeImportTest(TransactionCase):
@classmethod
def tearDownClass(cls):
fields._RelationalMulti.convert_to_cache = (
fields_convert_to_cache._original_method
)
models.BaseModel._update_cache = models_update_cache._original_method
super().tearDownClass()

def test_import_mdfe(self):
res_items = (
"mdfe",
Expand Down

0 comments on commit c465cbc

Please sign in to comment.