383 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
		
		
			
		
	
	
			383 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
|  | # Copyright 2017 Camptocamp SA | ||
|  | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) | ||
|  | 
 | ||
|  | from contextlib import contextmanager | ||
|  | 
 | ||
|  | from odoo.addons.component.core import Component | ||
|  | from odoo.addons.component.exception import NoComponentError, SeveralComponentError | ||
|  | 
 | ||
|  | from .common import TransactionComponentRegistryCase | ||
|  | 
 | ||
|  | 
 | ||
|  | 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().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 generic component match""" | ||
|  |         # we create 1 new Component with _usage 'for.test', in the same | ||
|  |         # collection and no _apply_on, and we remove the _apply_on of component | ||
|  |         # 1 so they are generic components for a collection | ||
|  |         class Component3(Component): | ||
|  |             _name = "component3" | ||
|  |             _collection = "collection.base" | ||
|  |             _usage = "for.test" | ||
|  | 
 | ||
|  |         class Component1(Component): | ||
|  |             _inherit = "component1" | ||
|  |             _collection = "collection.base" | ||
|  |             _usage = "for.test" | ||
|  |             _apply_on = None | ||
|  | 
 | ||
|  |         Component3._build_component(self.comp_registry) | ||
|  |         Component1._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: | ||
|  |                 # component3 (because it has no _apply_on so apply in any case) | ||
|  |                 # component4 (for the same reason) | ||
|  |                 base.component(usage="for.test") | ||
|  | 
 | ||
|  |     def test_component_error_several_same_model(self): | ||
|  |         """Use component(usage=...) when more than one component match a model""" | ||
|  |         # 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" | ||
|  |             _apply_on = ["res.partner"] | ||
|  | 
 | ||
|  |         Component3._build_component(self.comp_registry) | ||
|  | 
 | ||
|  |         with self.get_base() as base: | ||
|  |             with self.assertRaises(SeveralComponentError): | ||
|  |                 # Here, the candidates components would be: | ||
|  |                 # component1 (because we are working with res.partner), | ||
|  |                 # component3 (for the same reason) | ||
|  |                 base.component(usage="for.test") | ||
|  | 
 | ||
|  |     def test_component_specific_model(self): | ||
|  |         """Use component(usage=...) when more than one component match but
 | ||
|  |         only one for the specific model"""
 | ||
|  |         # we create a new Component with _usage 'for.test', in the same | ||
|  |         # collection and no _apply_on. This is a generic component for the | ||
|  |         # collection | ||
|  |         class Component3(Component): | ||
|  |             _name = "component3" | ||
|  |             _collection = "collection.base" | ||
|  |             _usage = "for.test" | ||
|  | 
 | ||
|  |         Component3._build_component(self.comp_registry) | ||
|  | 
 | ||
|  |         with self.get_base() as base: | ||
|  |             # 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). | ||
|  |             # When a component is specifically linked to a model with | ||
|  |             # _apply_on, it takes precedence over a generic component. It | ||
|  |             # allows to create a generic implementation (component3 here) and | ||
|  |             # override it only for a given model. So in this case, the final | ||
|  |             # component is component1. | ||
|  |             comp = base.component(usage="for.test") | ||
|  |             self.assertEqual("component1", comp._name) | ||
|  | 
 | ||
|  |     def test_component_specific_collection(self): | ||
|  |         """Use component(usage=...) when more than one component match but
 | ||
|  |         only one for the specific collection"""
 | ||
|  |         # we create a new Component with _usage 'for.test', without collection | ||
|  |         # and no _apply_on | ||
|  |         class Component3(Component): | ||
|  |             _name = "component3" | ||
|  |             _usage = "for.test" | ||
|  | 
 | ||
|  |         Component3._build_component(self.comp_registry) | ||
|  | 
 | ||
|  |         with self.get_base() as base: | ||
|  |             # 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). | ||
|  |             # When a component has no _collection, it means it can be applied | ||
|  |             # on all model if no component is found for the current collection: | ||
|  |             # component3 must be ignored since a component (component1) exists | ||
|  |             # and is specificaly linked to the expected collection. | ||
|  |             comp = base.component(usage="for.test") | ||
|  |             self.assertEqual("component1", comp._name) | ||
|  | 
 | ||
|  |     def test_component_specific_collection_specific_model(self): | ||
|  |         """Use component(usage=...) when more than one component match but
 | ||
|  |         only one for the specific model and collection"""
 | ||
|  |         # we create a new Component with _usage 'for.test', without collection | ||
|  |         # and no _apply_on. This is a component generic for all collections and | ||
|  |         # models | ||
|  |         class Component3(Component): | ||
|  |             _name = "component3" | ||
|  |             _usage = "for.test" | ||
|  | 
 | ||
|  |         Component3._build_component(self.comp_registry) | ||
|  | 
 | ||
|  |         with self.get_base() as base: | ||
|  |             # When a component has no _apply_on, it means it can be applied on | ||
|  |             # *any* model, no _collection, it can be applied on *any* | ||
|  |             # collection. | ||
|  |             # Here, the candidates components would be: | ||
|  |             # component1 (because we are working with res.partner), | ||
|  |             # component3 (because it has no _apply_on and no _collection so | ||
|  |             # apply in any case). | ||
|  |             # When a component is specifically linked to a model with | ||
|  |             # _apply_on, it takes precedence over a generic component, the same | ||
|  |             # happens for collection. It allows to create a generic | ||
|  |             # implementation (component3 here) and override it only for a given | ||
|  |             # collection and model. So in this case, the final component is | ||
|  |             # component1. | ||
|  |             comp = base.component(usage="for.test") | ||
|  |             self.assertEqual("component1", comp._name) | ||
|  | 
 | ||
|  |     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, **kw): | ||
|  |                 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) |