-
-
Notifications
You must be signed in to change notification settings - Fork 250
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
l10n_br_mdfe_spec: monkey patch to run tests
use to 2 monkey patches to be able to run the XML import tests against AbstractModel MDFe models
- Loading branch information
Showing
1 changed file
with
136 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
# Copyright 2020 Akretion - Raphael Valyi <[email protected]> | ||
# 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 | ||
|
@@ -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 | ||
|
||
|
@@ -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", | ||
|