# -*- coding: utf-8 -*- # Copyright 2017 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) from contextlib import contextmanager from odoo.addons.component.core import ( Component, ) from .common import TransactionComponentRegistryCase from odoo.addons.component.exception import ( NoComponentError, SeveralComponentError, ) class TestComponent(TransactionComponentRegistryCase): """ Test usage of components These tests are a bit more broad that mere unit tests. We test the chain odoo Model -> generate a WorkContext instance -> Work with Component. Tests are inside Odoo transactions, so we can work with Odoo's env / models. """ def setUp(self): super(TestComponent, self).setUp() self.collection = self.env['collection.base'] # create some Component to play with class Component1(Component): _name = 'component1' _collection = 'collection.base' _usage = 'for.test' _apply_on = ['res.partner'] class Component2(Component): _name = 'component2' _collection = 'collection.base' _usage = 'for.test' _apply_on = ['res.users'] # build the components and register them in our # test component registry Component1._build_component(self.comp_registry) Component2._build_component(self.comp_registry) # our collection, in a less abstract use case, it # could be a record of 'magento.backend' for instance self.collection_record = self.collection.new() @contextmanager def get_base(): # Our WorkContext, it will be passed along in every # components so we can share data transversally. # We are working with res.partner in the following tests, # unless we change it in the test. with self.collection_record.work_on( 'res.partner', # we use a custom registry only # for the sake of the tests components_registry=self.comp_registry ) as work: # We get the 'base' component, handy to test the base # methods component, many_components, ... yield work.component_by_name('base') self.get_base = get_base def test_component_attrs(self): """ Basic access to a Component's attribute """ with self.get_base() as base: # as we are working on res.partner, we should get 'component1' comp = base.work.component(usage='for.test') # but this is not what we test here, we test the attributes: self.assertEqual(self.collection_record, comp.collection) self.assertEqual(base.work, comp.work) self.assertEqual(self.env, comp.env) self.assertEqual(self.env['res.partner'], comp.model) def test_component_get_by_name_same_model(self): """ Use component_by_name with current working model """ with self.get_base() as base: # we ask a component directly by it's name, considering # we work with res.partner, we should get 'component1' # this is ok because it's _apply_on contains res.partner comp = base.component_by_name('component1') self.assertEqual('component1', comp._name) self.assertEqual(self.env['res.partner'], comp.model) def test_component_get_by_name_other_model(self): """ Use component_by_name with another model """ with self.get_base() as base: # we ask a component directly by it's name, but we # want to work with 'res.users', this is ok since # component2's _apply_on contains res.users comp = base.component_by_name( 'component2', model_name='res.users' ) self.assertEqual('component2', comp._name) self.assertEqual(self.env['res.users'], comp.model) # what happens under the hood, is that a new WorkContext # has been created for this model, with all the other values # identical to the previous WorkContext (the one for res.partner) # We can check that with: self.assertNotEqual(base.work, comp.work) self.assertEqual('res.partner', base.work.model_name) self.assertEqual('res.users', comp.work.model_name) def test_component_get_by_name_wrong_model(self): """ Use component_by_name with a model not in _apply_on """ msg = ("Component with name 'component2' can't be used " "for model 'res.partner'.*") with self.get_base() as base: with self.assertRaisesRegex(NoComponentError, msg): # we ask for the model 'component2' but we are working # with res.partner, and it only accepts res.users base.component_by_name('component2') def test_component_get_by_name_not_exist(self): """ Use component_by_name on a component that do not exist """ msg = "No component with name 'foo' found." with self.get_base() as base: with self.assertRaisesRegex(NoComponentError, msg): base.component_by_name('foo') def test_component_by_usage_same_model(self): """ Use component(usage=...) on the same model """ # we ask for a component having _usage == 'for.test', and # model being res.partner (the model in the current WorkContext) with self.get_base() as base: comp = base.component(usage='for.test') self.assertEqual('component1', comp._name) self.assertEqual(self.env['res.partner'], comp.model) def test_component_by_usage_other_model(self): """ Use component(usage=...) on a different model (name) """ # we ask for a component having _usage == 'for.test', and # a different model (res.users) with self.get_base() as base: comp = base.component(usage='for.test', model_name='res.users') self.assertEqual('component2', comp._name) self.assertEqual(self.env['res.users'], comp.model) # what happens under the hood, is that a new WorkContext # has been created for this model, with all the other values # identical to the previous WorkContext (the one for res.partner) # We can check that with: self.assertNotEqual(base.work, comp.work) self.assertEqual('res.partner', base.work.model_name) self.assertEqual('res.users', comp.work.model_name) def test_component_by_usage_other_model_env(self): """ Use component(usage=...) on a different model (instance) """ with self.get_base() as base: comp = base.component(usage='for.test', model_name=self.env['res.users']) self.assertEqual('component2', comp._name) self.assertEqual(self.env['res.users'], comp.model) def test_component_error_several(self): """ Use component(usage=...) when more than one component match """ # we create a new Component with _usage 'for.test', in the same # collection and no _apply_on class Component3(Component): _name = 'component3' _collection = 'collection.base' _usage = 'for.test' Component3._build_component(self.comp_registry) with self.get_base() as base: with self.assertRaises(SeveralComponentError): # When a component has no _apply_on, it means it can be applied # on *any* model. Here, the candidates components would be: # component1 (because we are working with res.partner), # component3 (because it has no _apply_on so apply in any case) base.component(usage='for.test') def test_many_components(self): """ Use many_components(usage=...) on the same model """ class Component3(Component): _name = 'component3' _collection = 'collection.base' _usage = 'for.test' Component3._build_component(self.comp_registry) with self.get_base() as base: comps = base.many_components(usage='for.test') # When a component has no _apply_on, it means it can be applied # on *any* model. So here, both component1 and component3 match self.assertEqual( ['component1', 'component3'], [c._name for c in comps] ) def test_many_components_other_model(self): """ Use many_components(usage=...) on a different model (name) """ class Component3(Component): _name = 'component3' _collection = 'collection.base' _apply_on = 'res.users' _usage = 'for.test' Component3._build_component(self.comp_registry) with self.get_base() as base: comps = base.many_components(usage='for.test', model_name='res.users') self.assertEqual( ['component2', 'component3'], [c._name for c in comps] ) def test_many_components_other_model_env(self): """ Use many_components(usage=...) on a different model (instance) """ class Component3(Component): _name = 'component3' _collection = 'collection.base' _apply_on = 'res.users' _usage = 'for.test' Component3._build_component(self.comp_registry) with self.get_base() as base: comps = base.many_components(usage='for.test', model_name=self.env['res.users']) self.assertEqual( ['component2', 'component3'], [c._name for c in comps] ) def test_no_component(self): """ No component found for asked usage """ with self.get_base() as base: with self.assertRaises(NoComponentError): base.component(usage='foo') def test_no_many_component(self): """ No component found for asked usage for many_components() """ with self.get_base() as base: self.assertEqual([], base.many_components(usage='foo')) def test_work_on_component(self): """ Check WorkContext.component() (shortcut to Component.component) """ with self.get_base() as base: comp = base.work.component(usage='for.test') self.assertEqual('component1', comp._name) def test_work_on_many_components(self): """ Check WorkContext.many_components() (shortcut to Component.many_components) """ with self.get_base() as base: comps = base.work.many_components(usage='for.test') self.assertEqual('component1', comps[0]._name) def test_component_match(self): """ Lookup with match method """ class Foo(Component): _name = 'foo' _collection = 'collection.base' _usage = 'speaker' _apply_on = ['res.partner'] @classmethod def _component_match(cls, work): return False class Bar(Component): _name = 'bar' _collection = 'collection.base' _usage = 'speaker' _apply_on = ['res.partner'] self._build_components(Foo, Bar) with self.get_base() as base: # both components would we returned without the # _component_match method comp = base.component(usage='speaker', model_name=self.env['res.partner']) self.assertEqual('bar', comp._name)