mirror of
https://github.com/bringout/oca-technical.git
synced 2026-04-18 13:52:05 +02:00
Initial commit: OCA Technical packages (595 packages)
This commit is contained in:
commit
2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions
|
|
@ -0,0 +1,6 @@
|
|||
from . import common
|
||||
from . import test_cerberus_list_validator
|
||||
from . import test_cerberus_validator
|
||||
from . import test_controller_builder
|
||||
from . import test_openapi_generator
|
||||
from . import test_service_context_provider
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
# Copyright 2017 Akretion (http://www.akretion.com).
|
||||
# Copyright 2020 ACSONE SA/NV
|
||||
# @author Sébastien BEAU <sebastien.beau@akretion.com>
|
||||
# @author Laurent Mignon <laurent.mignon@acsone.eu>
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
import copy
|
||||
|
||||
from odoo import http
|
||||
from odoo.tests.common import TransactionCase, get_db_name
|
||||
|
||||
from odoo.addons.component.core import (
|
||||
WorkContext,
|
||||
_component_databases,
|
||||
_get_addon_name,
|
||||
)
|
||||
from odoo.addons.component.tests.common import (
|
||||
ComponentRegistryCase,
|
||||
TransactionComponentCase,
|
||||
new_rollbacked_env,
|
||||
)
|
||||
|
||||
from ..controllers.main import RestController, _PseudoCollection
|
||||
from ..core import (
|
||||
RestServicesRegistry,
|
||||
_rest_controllers_per_module,
|
||||
_rest_services_databases,
|
||||
)
|
||||
from ..tools import ROUTING_DECORATOR_ATTR, _inspect_methods
|
||||
|
||||
|
||||
class RegistryMixin(object):
|
||||
@classmethod
|
||||
def setUpRegistry(cls):
|
||||
with new_rollbacked_env() as env:
|
||||
service_registration = env["rest.service.registration"]
|
||||
# build the registry of every installed addons
|
||||
services_registry = service_registration._init_global_registry()
|
||||
cls._services_registry = services_registry
|
||||
# ensure that we load only the services of the 'installed'
|
||||
# modules, not 'to install', which means we load only the
|
||||
# dependencies of the tested addons, not the siblings or
|
||||
# children addons
|
||||
service_registration.build_registry(
|
||||
services_registry, states=("installed",)
|
||||
)
|
||||
# build the services of the current tested addon
|
||||
current_addon = _get_addon_name(cls.__module__)
|
||||
service_registration.load_services(current_addon, services_registry)
|
||||
env["rest.service.registration"]._build_controllers_routes(
|
||||
services_registry
|
||||
)
|
||||
|
||||
|
||||
class RestServiceRegistryCase(ComponentRegistryCase):
|
||||
|
||||
# pylint: disable=W8106
|
||||
@staticmethod
|
||||
def _setup_registry(class_or_instance):
|
||||
ComponentRegistryCase._setup_registry(class_or_instance)
|
||||
|
||||
class_or_instance._service_registry = RestServicesRegistry()
|
||||
# take a copy of registered controllers
|
||||
class_or_instance._controller_children_classes = copy.deepcopy(
|
||||
http.Controller.children_classes
|
||||
)
|
||||
class_or_instance._original_addon_rest_controllers_per_module = copy.deepcopy(
|
||||
_rest_controllers_per_module[_get_addon_name(class_or_instance.__module__)]
|
||||
)
|
||||
db_name = get_db_name()
|
||||
|
||||
# makes the test component registry available for the db name
|
||||
class_or_instance._original_component_databases = _component_databases.get(
|
||||
db_name
|
||||
)
|
||||
_component_databases[db_name] = class_or_instance.comp_registry
|
||||
|
||||
# makes the test service registry available for the db name
|
||||
class_or_instance._original_services_registry = _rest_services_databases.get(
|
||||
db_name, {}
|
||||
)
|
||||
_rest_services_databases[db_name] = class_or_instance._service_registry
|
||||
|
||||
# build the services and controller of every installed addons
|
||||
# but the current addon (when running with pytest/nosetest, we
|
||||
# simulate the --test-enable behavior by excluding the current addon
|
||||
# which is in 'to install' / 'to upgrade' with --test-enable).
|
||||
current_addon = _get_addon_name(class_or_instance.__module__)
|
||||
|
||||
with new_rollbacked_env() as env:
|
||||
RestServiceRegistration = env["rest.service.registration"]
|
||||
RestServiceRegistration.build_registry(
|
||||
class_or_instance._service_registry,
|
||||
states=("installed",),
|
||||
exclude_addons=[current_addon],
|
||||
)
|
||||
RestServiceRegistration._build_controllers_routes(
|
||||
class_or_instance._service_registry
|
||||
)
|
||||
|
||||
# register our components
|
||||
class_or_instance.comp_registry.load_components("base_rest")
|
||||
|
||||
# Define a base test controller here to avoid to have this controller
|
||||
# registered outside tests
|
||||
class_or_instance._collection_name = "base.rest.test"
|
||||
|
||||
BaseTestController = class_or_instance._get_test_controller(class_or_instance)
|
||||
|
||||
class_or_instance._BaseTestController = BaseTestController
|
||||
class_or_instance._controller_route_method_names = {
|
||||
"my_controller_route_without",
|
||||
"my_controller_route_with",
|
||||
"my_controller_route_without_auth_2",
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_test_controller(class_or_instance, root_path="/test_controller/"):
|
||||
class BaseTestController(RestController):
|
||||
_root_path = root_path
|
||||
_collection_name = class_or_instance._collection_name
|
||||
_default_auth = "public"
|
||||
|
||||
@http.route("/my_controller_route_without")
|
||||
def my_controller_route_without(self):
|
||||
return {}
|
||||
|
||||
@http.route(
|
||||
"/my_controller_route_with",
|
||||
auth="public",
|
||||
cors="http://with_cors",
|
||||
csrf="False",
|
||||
save_session="False",
|
||||
)
|
||||
def my_controller_route_with(self):
|
||||
return {}
|
||||
|
||||
@http.route("/my_controller_route_without_auth_2", auth=None)
|
||||
def my_controller_route_without_auth_2(self):
|
||||
return {}
|
||||
|
||||
return BaseTestController
|
||||
|
||||
@staticmethod
|
||||
def _teardown_registry(class_or_instance):
|
||||
ComponentRegistryCase._teardown_registry(class_or_instance)
|
||||
http.Controller.children_classes = (
|
||||
class_or_instance._controller_children_classes
|
||||
)
|
||||
db_name = get_db_name()
|
||||
_component_databases[db_name] = class_or_instance._original_component_databases
|
||||
_rest_services_databases[
|
||||
db_name
|
||||
] = class_or_instance._original_services_registry
|
||||
class_or_instance._service_registry = {}
|
||||
_rest_controllers_per_module[
|
||||
_get_addon_name(class_or_instance.__module__)
|
||||
] = class_or_instance._original_addon_rest_controllers_per_module
|
||||
|
||||
@staticmethod
|
||||
def _build_services(class_or_instance, *classes):
|
||||
class_or_instance._build_components(*classes)
|
||||
with new_rollbacked_env() as env:
|
||||
RestServiceRegistration = env["rest.service.registration"]
|
||||
current_addon = _get_addon_name(class_or_instance.__module__)
|
||||
RestServiceRegistration.load_services(
|
||||
current_addon, class_or_instance._service_registry
|
||||
)
|
||||
RestServiceRegistration._build_controllers_routes(
|
||||
class_or_instance._service_registry
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_controller_for(service, addon="base_rest"):
|
||||
identifier = "{}_{}_{}".format(
|
||||
get_db_name(),
|
||||
service._collection.replace(".", "_"),
|
||||
service._usage.replace(".", "_"),
|
||||
)
|
||||
controllers = [
|
||||
controller
|
||||
for controller in http.Controller.children_classes.get(addon, [])
|
||||
if getattr(controller, "_identifier", None) == identifier
|
||||
]
|
||||
if not controllers:
|
||||
return
|
||||
return controllers[-1]
|
||||
|
||||
@staticmethod
|
||||
def _get_controller_route_methods(controller):
|
||||
methods = {}
|
||||
for name, method in _inspect_methods(controller):
|
||||
if hasattr(method, ROUTING_DECORATOR_ATTR):
|
||||
methods[name] = method
|
||||
return methods
|
||||
|
||||
@staticmethod
|
||||
def _get_service_component(class_or_instance, usage, collection=None):
|
||||
collection = collection or _PseudoCollection(
|
||||
class_or_instance._collection_name, class_or_instance.env
|
||||
)
|
||||
work = WorkContext(
|
||||
model_name="rest.service.registration",
|
||||
collection=collection,
|
||||
components_registry=class_or_instance.comp_registry,
|
||||
)
|
||||
return work.component(usage=usage)
|
||||
|
||||
|
||||
class TransactionRestServiceRegistryCase(TransactionCase, RestServiceRegistryCase):
|
||||
"""Adds Odoo Transaction to inherited from ComponentRegistryCase.
|
||||
|
||||
This class doesn't set up the registry for you.
|
||||
You're supposed to explicitly call `_setup_registry` and `_teardown_registry`
|
||||
when you need it, either on setUpClass and tearDownClass or setUp and tearDown.
|
||||
|
||||
class MyTestCase(TransactionRestServiceRegistryCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self._setup_registry(self)
|
||||
|
||||
def tearDown(self):
|
||||
self._teardown_registry(self)
|
||||
super().tearDown()
|
||||
|
||||
class MyTestCase(TransactionRestServiceRegistryCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._setup_registry(cls)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls._teardown_registry(cls)
|
||||
super().tearDownClass()
|
||||
"""
|
||||
|
||||
# pylint: disable=W8106
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# resolve an inheritance issue (common.TransactionCase does not use
|
||||
# super)
|
||||
TransactionCase.setUpClass()
|
||||
cls.base_url = cls.env["ir.config_parameter"].get_param("web.base.url")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
TransactionCase.tearDownClass()
|
||||
|
||||
|
||||
class BaseRestCase(TransactionComponentCase, RegistryMixin):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.setUpRegistry()
|
||||
cls.base_url = cls.env["ir.config_parameter"].get_param("web.base.url")
|
||||
cls.registry.enter_test_mode(cls.env.cr)
|
||||
|
||||
# pylint: disable=W8110
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.registry.leave_test_mode()
|
||||
super().tearDownClass()
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
# Copyright 2020 ACSONE SA/NV
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
import unittest
|
||||
|
||||
from cerberus import Validator
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import BaseCase, MetaCase
|
||||
|
||||
from ..components.cerberus_validator import BaseRestCerberusValidator
|
||||
from ..restapi import CerberusListValidator
|
||||
|
||||
|
||||
class TestCerberusListValidator(BaseCase, MetaCase("DummyCase", (object,), {})):
|
||||
"""Test all the methods that must be implemented by CerberusListValidator to
|
||||
be a valid RestMethodParam"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.simple_schema = {
|
||||
"name": {"type": "string", "required": True, "nullable": True},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"nullable": False,
|
||||
"required": False,
|
||||
"allowed": ["mr", "mm"],
|
||||
},
|
||||
}
|
||||
|
||||
cls.nested_schema = {
|
||||
"name": {"type": "string", "required": True, "empty": False},
|
||||
"country": {
|
||||
"type": "dict",
|
||||
"schema": {
|
||||
"id": {"type": "integer", "required": True, "nullable": False},
|
||||
"name": {"type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
cls.simple_schema_list_validator = CerberusListValidator(
|
||||
schema=cls.simple_schema, min_items=1, max_items=2, unique_items=True
|
||||
)
|
||||
cls.nested_schema_list_validator = CerberusListValidator(
|
||||
schema=cls.nested_schema
|
||||
)
|
||||
cls.maxDiff = None
|
||||
|
||||
def test_to_openapi_responses(self):
|
||||
res = self.simple_schema_list_validator.to_openapi_responses(None, None)
|
||||
self.assertDictEqual(
|
||||
res,
|
||||
{
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": {"nullable": True, "type": "string"},
|
||||
"title": {
|
||||
"enum": ["mr", "mm"],
|
||||
"nullable": False,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"maxItems": 2,
|
||||
"minItems": 1,
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
res = self.nested_schema_list_validator.to_openapi_responses(None, None)
|
||||
self.assertDictEqual(
|
||||
res,
|
||||
{
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"country": {
|
||||
"type": "object",
|
||||
"required": ["id"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"nullable": False,
|
||||
"type": "integer",
|
||||
},
|
||||
"name": {"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"type": "array",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def test_to_openapi_requestbody(self):
|
||||
res = self.simple_schema_list_validator.to_openapi_requestbody(None, None)
|
||||
self.assertEqual(
|
||||
res,
|
||||
{
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": {"nullable": True, "type": "string"},
|
||||
"title": {
|
||||
"enum": ["mr", "mm"],
|
||||
"nullable": False,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"maxItems": 2,
|
||||
"minItems": 1,
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
res = self.nested_schema_list_validator.to_openapi_requestbody(None, None)
|
||||
self.assertDictEqual(
|
||||
res,
|
||||
{
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"country": {
|
||||
"type": "object",
|
||||
"required": ["id"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"nullable": False,
|
||||
"type": "integer",
|
||||
},
|
||||
"name": {"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"type": "array",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def test_to_openapi_query_parameters(self):
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.simple_schema_list_validator.to_openapi_query_parameters(None, None)
|
||||
|
||||
def test_from_params_ignore_unknown(self):
|
||||
params = [{"name": "test", "unknown": True}]
|
||||
res = self.simple_schema_list_validator.from_params(None, params=params)
|
||||
self.assertListEqual(res, [{"name": "test"}])
|
||||
|
||||
def test_from_params_validation(self):
|
||||
# minItems / maxItems
|
||||
with self.assertRaises(UserError):
|
||||
# minItems = 1
|
||||
self.simple_schema_list_validator.from_params(None, params=[])
|
||||
with self.assertRaises(UserError):
|
||||
# maxItems = 2
|
||||
self.simple_schema_list_validator.from_params(
|
||||
None, params=[{"name": "test"}, {"name": "test"}, {"name": "test"}]
|
||||
)
|
||||
with self.assertRaises(UserError):
|
||||
# name required
|
||||
self.simple_schema_list_validator.from_params(None, params=[{}])
|
||||
|
||||
def test_to_response_ignore_unknown(self):
|
||||
result = [{"name": "test", "unknown": True}]
|
||||
res = self.simple_schema_list_validator.to_response(None, result=result)
|
||||
self.assertListEqual(res, [{"name": "test"}])
|
||||
|
||||
def test_to_response_validation(self):
|
||||
# If a response is not conform to the expected schema it's considered
|
||||
# as a programmatic error not a user error
|
||||
with self.assertRaises(SystemError):
|
||||
# minItems = 1
|
||||
self.simple_schema_list_validator.to_response(None, result=[])
|
||||
with self.assertRaises(SystemError):
|
||||
# maxItems = 2
|
||||
self.simple_schema_list_validator.to_response(
|
||||
None, result=[{"name": "test"}, {"name": "test"}, {"name": "test"}]
|
||||
)
|
||||
with self.assertRaises(SystemError):
|
||||
# name required
|
||||
self.simple_schema_list_validator.to_response(None, result=[{}])
|
||||
|
||||
def test_schema_lookup_from_string(self):
|
||||
class MyService(object):
|
||||
def _get_simple_schema(self):
|
||||
return {"name": {"type": "string", "required": True, "nullable": True}}
|
||||
|
||||
def component(self, *args, **kwargs):
|
||||
return BaseRestCerberusValidator(unittest.mock.Mock())
|
||||
|
||||
v = CerberusListValidator(schema="_get_simple_schema")
|
||||
validator = v.get_cerberus_validator(MyService(), "output")
|
||||
self.assertTrue(validator)
|
||||
self.assertDictEqual(
|
||||
validator.root_schema.schema,
|
||||
{"name": {"type": "string", "required": True, "nullable": True}},
|
||||
)
|
||||
|
||||
def test_schema_lookup_from_string_custom_validator(self):
|
||||
class MyService(object):
|
||||
def _get_simple_schema(self):
|
||||
return Validator(
|
||||
{"name": {"type": "string", "required": False}}, require_all=True
|
||||
)
|
||||
|
||||
def component(self, *args, **kwargs):
|
||||
return BaseRestCerberusValidator(unittest.mock.Mock())
|
||||
|
||||
v = CerberusListValidator(schema="_get_simple_schema")
|
||||
validator = v.get_cerberus_validator(MyService(), "input")
|
||||
self.assertTrue(validator.require_all)
|
||||
|
|
@ -0,0 +1,449 @@
|
|||
# Copyright 2020 ACSONE SA/NV
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
import unittest
|
||||
|
||||
from cerberus import Validator
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import BaseCase, MetaCase
|
||||
|
||||
from ..components.cerberus_validator import BaseRestCerberusValidator
|
||||
from ..restapi import CerberusValidator
|
||||
from ..tools import cerberus_to_json
|
||||
|
||||
|
||||
class TestCerberusValidator(BaseCase, MetaCase("DummyCase", (object,), {})):
|
||||
"""Test all the methods that must be implemented by CerberusValidator to
|
||||
be a valid RestMethodParam"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.simple_schema = {
|
||||
"name": {"type": "string", "required": True, "nullable": True},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"nullable": False,
|
||||
"required": False,
|
||||
"allowed": ["mr", "mm"],
|
||||
},
|
||||
"age": {"type": "integer", "default": 18},
|
||||
"interests": {"type": "list", "schema": {"type": "string"}},
|
||||
}
|
||||
|
||||
cls.nested_schema = {
|
||||
"name": {"type": "string", "required": True, "empty": False},
|
||||
"country": {
|
||||
"type": "dict",
|
||||
"schema": {
|
||||
"id": {"type": "integer", "required": True, "nullable": False},
|
||||
"name": {"type": "string"},
|
||||
},
|
||||
},
|
||||
"is_company": {"type": "boolean"},
|
||||
}
|
||||
cls.simple_schema_cerberus_validator = CerberusValidator(
|
||||
schema=cls.simple_schema
|
||||
)
|
||||
cls.nested_schema_cerberus_validator = CerberusValidator(
|
||||
schema=cls.nested_schema
|
||||
)
|
||||
|
||||
def test_to_openapi_responses(self):
|
||||
res = self.simple_schema_cerberus_validator.to_openapi_responses(None, None)
|
||||
self.assertDictEqual(
|
||||
res,
|
||||
{
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": {"nullable": True, "type": "string"},
|
||||
"title": {
|
||||
"enum": ["mr", "mm"],
|
||||
"nullable": False,
|
||||
"type": "string",
|
||||
},
|
||||
"age": {"default": 18, "type": "integer"},
|
||||
"interests": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
res = self.nested_schema_cerberus_validator.to_openapi_responses(None, None)
|
||||
self.assertDictEqual(
|
||||
res,
|
||||
{
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"country": {
|
||||
"type": "object",
|
||||
"required": ["id"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"nullable": False,
|
||||
"type": "integer",
|
||||
},
|
||||
"name": {"type": "string"},
|
||||
},
|
||||
},
|
||||
"is_company": {"type": "boolean"},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def test_to_openapi_requestbody(self):
|
||||
res = self.simple_schema_cerberus_validator.to_openapi_requestbody(None, None)
|
||||
self.assertEqual(
|
||||
res,
|
||||
{
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": {"nullable": True, "type": "string"},
|
||||
"title": {
|
||||
"enum": ["mr", "mm"],
|
||||
"nullable": False,
|
||||
"type": "string",
|
||||
},
|
||||
"age": {"default": 18, "type": "integer"},
|
||||
"interests": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
res = self.nested_schema_cerberus_validator.to_openapi_requestbody(None, None)
|
||||
self.assertDictEqual(
|
||||
res,
|
||||
{
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"country": {
|
||||
"type": "object",
|
||||
"required": ["id"],
|
||||
"properties": {
|
||||
"id": {"nullable": False, "type": "integer"},
|
||||
"name": {"type": "string"},
|
||||
},
|
||||
},
|
||||
"is_company": {"type": "boolean"},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def test_to_openapi_query_parameters(self):
|
||||
res = self.simple_schema_cerberus_validator.to_openapi_query_parameters(
|
||||
None, None
|
||||
)
|
||||
self.assertListEqual(
|
||||
res,
|
||||
[
|
||||
{
|
||||
"name": "name",
|
||||
"in": "query",
|
||||
"required": True,
|
||||
"allowEmptyValue": True,
|
||||
"default": None,
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"allowEmptyValue": False,
|
||||
"default": None,
|
||||
"schema": {"type": "string", "enum": ["mr", "mm"]},
|
||||
},
|
||||
{
|
||||
"name": "age",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"allowEmptyValue": False,
|
||||
"default": 18,
|
||||
"schema": {"type": "integer"},
|
||||
},
|
||||
{
|
||||
"name": "interests[]",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"allowEmptyValue": False,
|
||||
"default": None,
|
||||
"schema": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
],
|
||||
)
|
||||
res = self.nested_schema_cerberus_validator.to_openapi_query_parameters(
|
||||
None, None
|
||||
)
|
||||
self.assertListEqual(
|
||||
res,
|
||||
[
|
||||
{
|
||||
"name": "name",
|
||||
"in": "query",
|
||||
"required": True,
|
||||
"allowEmptyValue": False,
|
||||
"default": None,
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
{
|
||||
"name": "country",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"allowEmptyValue": False,
|
||||
"default": None,
|
||||
"schema": {"type": "object"},
|
||||
},
|
||||
{
|
||||
"name": "is_company",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"allowEmptyValue": False,
|
||||
"default": None,
|
||||
"schema": {"type": "boolean"},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
def test_from_params_add_default(self):
|
||||
params = {"name": "test"}
|
||||
res = self.simple_schema_cerberus_validator.from_params(None, params=params)
|
||||
self.assertDictEqual(res, {"name": "test", "age": 18})
|
||||
|
||||
def test_from_params_ignore_unknown(self):
|
||||
params = {"name": "test", "unknown": True}
|
||||
res = self.simple_schema_cerberus_validator.from_params(None, params=params)
|
||||
self.assertDictEqual(res, {"name": "test", "age": 18})
|
||||
|
||||
def test_from_params_validation(self):
|
||||
# name is required
|
||||
with self.assertRaises(UserError):
|
||||
self.simple_schema_cerberus_validator.from_params(None, params={})
|
||||
|
||||
def test_to_response_add_default(self):
|
||||
result = {"name": "test"}
|
||||
res = self.simple_schema_cerberus_validator.to_response(None, result=result)
|
||||
self.assertDictEqual(res, {"name": "test", "age": 18})
|
||||
|
||||
def test_to_response_ignore_unknown(self):
|
||||
result = {"name": "test", "unknown": True}
|
||||
res = self.simple_schema_cerberus_validator.to_response(None, result=result)
|
||||
self.assertDictEqual(res, {"name": "test", "age": 18})
|
||||
|
||||
def test_to_response_validation(self):
|
||||
# name is required
|
||||
# If a response is not conform to the expected schema it's considered
|
||||
# as a programmatic error not a user error
|
||||
with self.assertRaises(SystemError):
|
||||
self.simple_schema_cerberus_validator.to_response(None, result={})
|
||||
|
||||
def test_schema_lookup_from_string(self):
|
||||
class MyService(object):
|
||||
def _get_simple_schema(self):
|
||||
return {"name": {"type": "string", "required": True, "nullable": True}}
|
||||
|
||||
def component(self, *args, **kwargs):
|
||||
return BaseRestCerberusValidator(unittest.mock.Mock())
|
||||
|
||||
v = CerberusValidator(schema="_get_simple_schema")
|
||||
validator = v.get_cerberus_validator(MyService(), "output")
|
||||
self.assertTrue(validator)
|
||||
self.assertDictEqual(
|
||||
validator.root_schema.schema,
|
||||
{"name": {"type": "string", "required": True, "nullable": True}},
|
||||
)
|
||||
|
||||
def test_schema_lookup_from_string_custom_validator(self):
|
||||
class MyService(object):
|
||||
def _get_simple_schema(self):
|
||||
return Validator(
|
||||
{"name": {"type": "string", "required": False}}, require_all=True
|
||||
)
|
||||
|
||||
def component(self, *args, **kwargs):
|
||||
return BaseRestCerberusValidator(unittest.mock.Mock())
|
||||
|
||||
v = CerberusValidator(schema="_get_simple_schema")
|
||||
validator = v.get_cerberus_validator(MyService(), "input")
|
||||
self.assertTrue(validator.require_all)
|
||||
|
||||
def test_custom_validator_handler(self):
|
||||
assertEq = self.assertEqual
|
||||
|
||||
class CustomBaseRestCerberusValidator(BaseRestCerberusValidator):
|
||||
def get_validator_handler(self, service, method_name, direction):
|
||||
# In your implementation, this is where you can handle how the
|
||||
# validator is retrieved / computed (dispatch to dedicated
|
||||
# components...).
|
||||
assertEq(service, my_service)
|
||||
assertEq(method_name, "my_endpoint")
|
||||
assertEq(direction, "input")
|
||||
# A callable with no parameter is expected.
|
||||
return lambda: Validator(
|
||||
{"name": {"type": "string", "required": False}}, require_all=True
|
||||
)
|
||||
|
||||
def has_validator_handler(self, service, method_name, direction):
|
||||
return True
|
||||
|
||||
class MyService(object):
|
||||
def component(self, *args, **kwargs):
|
||||
return CustomBaseRestCerberusValidator(unittest.mock.Mock())
|
||||
|
||||
my_service = MyService()
|
||||
|
||||
v = CerberusValidator(schema="my_endpoint")
|
||||
validator = v.get_cerberus_validator(my_service, "input")
|
||||
self.assertTrue(validator.require_all)
|
||||
|
||||
def test_cerberus_key_value_mapping_to_openapi(self):
|
||||
schema = {
|
||||
"indexes": {
|
||||
"type": "dict",
|
||||
"required": True,
|
||||
"nullable": True,
|
||||
"keysrules": {"type": "string"},
|
||||
"valuesrules": {"type": "string"},
|
||||
}
|
||||
}
|
||||
openapi = cerberus_to_json(schema)
|
||||
self.assertDictEqual(
|
||||
openapi,
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["indexes"],
|
||||
"properties": {
|
||||
"indexes": {
|
||||
"nullable": True,
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "string"},
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
schema = {
|
||||
"indexes": {
|
||||
"type": "dict",
|
||||
"required": True,
|
||||
"nullable": True,
|
||||
"keysrules": {"type": "string"},
|
||||
"valuesrules": {
|
||||
"type": "dict",
|
||||
"schema": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"required": True,
|
||||
"nullable": False,
|
||||
},
|
||||
"name": {"type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
openapi = cerberus_to_json(schema)
|
||||
self.assertDictEqual(
|
||||
openapi,
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["indexes"],
|
||||
"properties": {
|
||||
"indexes": {
|
||||
"nullable": True,
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"required": ["id"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"nullable": False,
|
||||
"type": "integer",
|
||||
},
|
||||
"name": {"type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
def test_cerberus_meta_to_openapi(self):
|
||||
schema = {
|
||||
"indexes": {
|
||||
"type": "dict",
|
||||
"meta": {
|
||||
"description": "A key/value mapping where Key is the model "
|
||||
"name used to fill the index and value is the "
|
||||
"index name",
|
||||
"example": {
|
||||
"shopinvader.category": "demo_elasticsearch_backend_"
|
||||
"shopinvader_category_en_US",
|
||||
"shopinvader.variant": "demo_elasticsearch_backend_"
|
||||
"shopinvader_variant_en_US",
|
||||
},
|
||||
},
|
||||
"required": True,
|
||||
"nullable": True,
|
||||
"keysrules": {"type": "string"},
|
||||
"valuesrules": {"type": "string"},
|
||||
}
|
||||
}
|
||||
openapi = cerberus_to_json(schema)
|
||||
self.assertDictEqual(
|
||||
openapi,
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["indexes"],
|
||||
"properties": {
|
||||
"indexes": {
|
||||
"description": "A key/value mapping where Key is the model "
|
||||
"name used to fill the index and value is "
|
||||
"the index name",
|
||||
"example": {
|
||||
"shopinvader.category": "demo_elasticsearch_backend_"
|
||||
"shopinvader_category_en_US",
|
||||
"shopinvader.variant": "demo_elasticsearch_backend_"
|
||||
"shopinvader_variant_en_US",
|
||||
},
|
||||
"nullable": True,
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "string"},
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
@ -0,0 +1,671 @@
|
|||
# Copyright 2020 ACSONE SA/NV
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
from contextlib import contextmanager
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
|
||||
from .. import restapi
|
||||
from ..tools import ROUTING_DECORATOR_ATTR
|
||||
from .common import TransactionRestServiceRegistryCase
|
||||
|
||||
|
||||
class TestControllerBuilder(TransactionRestServiceRegistryCase):
|
||||
"""Test Odoo controller builder
|
||||
|
||||
In this class we test the generation of odoo controllers from the services
|
||||
component
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self._setup_registry(self)
|
||||
|
||||
def tearDown(self):
|
||||
self._teardown_registry(self)
|
||||
super().tearDown()
|
||||
|
||||
def test_01(self):
|
||||
"""Test controller generated for old API services
|
||||
|
||||
In this test we check that the controller generated for services with
|
||||
methods not decorated with the restapi.method decorator contains
|
||||
the required method to route requests to services. In the original
|
||||
implementation, these routes where hardcoded into the base controller.
|
||||
"""
|
||||
|
||||
# pylint: disable=R7980
|
||||
class TestServiceOldApi(Component):
|
||||
_inherit = "base.rest.service"
|
||||
_name = "test.ping.service"
|
||||
_usage = "ping"
|
||||
_collection = self._collection_name
|
||||
_description = "test"
|
||||
|
||||
def get(self, _id, message):
|
||||
pass
|
||||
|
||||
def search(self, message):
|
||||
return {"response": "Search called search with message " + message}
|
||||
|
||||
def update(self, _id, message):
|
||||
return {"response": "PUT called with message " + message}
|
||||
|
||||
# pylint:disable=method-required-super
|
||||
def create(self, **params):
|
||||
return {"response": "POST called with message " + params["message"]}
|
||||
|
||||
def delete(self, _id):
|
||||
return {"response": "DELETE called with id %s " % _id}
|
||||
|
||||
def my_method(self, **params):
|
||||
pass
|
||||
|
||||
def my_instance_method(self, _id, **params):
|
||||
pass
|
||||
|
||||
# Validator
|
||||
def _validator_search(self):
|
||||
return {"message": {"type": "string"}}
|
||||
|
||||
# Validator
|
||||
def _validator_get(self):
|
||||
# no parameters by default
|
||||
return {}
|
||||
|
||||
def _validator_update(self):
|
||||
return {"message": {"type": "string"}}
|
||||
|
||||
def _validator_create(self):
|
||||
return {"message": {"type": "string"}}
|
||||
|
||||
def _validator_my_method(self):
|
||||
return {"message": {"type": "string"}}
|
||||
|
||||
def _validator_my_instance_method(self):
|
||||
return {"message": {"type": "string"}}
|
||||
|
||||
self.assertFalse(self._get_controller_for(TestServiceOldApi))
|
||||
self._build_services(self, TestServiceOldApi)
|
||||
controller = self._get_controller_for(TestServiceOldApi)
|
||||
|
||||
routes = self._get_controller_route_methods(controller)
|
||||
self.assertSetEqual(
|
||||
set(routes.keys()),
|
||||
{
|
||||
"get_get",
|
||||
"get_search",
|
||||
"post_update",
|
||||
"put_update",
|
||||
"post_create",
|
||||
"post_delete",
|
||||
"delete_delete",
|
||||
"post_my_method",
|
||||
"post_my_instance_method",
|
||||
}
|
||||
| self._controller_route_method_names,
|
||||
)
|
||||
self.assertTrue(controller)
|
||||
# the generated method_name is always the {http_method}_{method_name}
|
||||
method = routes["get_get"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["GET"],
|
||||
"auth": "public",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": [
|
||||
"/test_controller/ping/<int:id>/get",
|
||||
"/test_controller/ping/<int:id>",
|
||||
],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
method = routes["get_search"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["GET"],
|
||||
"auth": "public",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": ["/test_controller/ping/search", "/test_controller/ping/"],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
method = routes["post_update"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["POST"],
|
||||
"auth": "public",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": [
|
||||
"/test_controller/ping/<int:id>/update",
|
||||
"/test_controller/ping/<int:id>",
|
||||
],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
method = routes["put_update"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["PUT"],
|
||||
"auth": "public",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": ["/test_controller/ping/<int:id>"],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
method = routes["post_create"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["POST"],
|
||||
"auth": "public",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": ["/test_controller/ping/create", "/test_controller/ping/"],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
method = routes["post_delete"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["POST"],
|
||||
"auth": "public",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": ["/test_controller/ping/<int:id>/delete"],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
method = routes["delete_delete"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["DELETE"],
|
||||
"auth": "public",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": ["/test_controller/ping/<int:id>"],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
method = routes["post_my_method"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["POST"],
|
||||
"auth": "public",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": ["/test_controller/ping/my_method"],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
method = routes["post_my_instance_method"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["POST"],
|
||||
"auth": "public",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": ["/test_controller/ping/<int:id>/my_instance_method"],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
def test_02(self):
|
||||
"""Test controller generated from services with new API methods
|
||||
|
||||
In this case we check that the generated controller for a service
|
||||
where the methods are decorated with restapi.method contains the
|
||||
required method to route the requests to the methods
|
||||
"""
|
||||
|
||||
# pylint: disable=R7980
|
||||
class TestServiceNewApi(Component):
|
||||
_inherit = "base.rest.service"
|
||||
_name = "test.partner.service"
|
||||
_usage = "partner"
|
||||
_collection = self._collection_name
|
||||
_description = "test"
|
||||
|
||||
@restapi.method(
|
||||
[(["/<int:id>/get", "/<int:id>"], "GET")],
|
||||
output_param=restapi.CerberusValidator("_get_partner_schema"),
|
||||
auth="public",
|
||||
)
|
||||
def get(self, _id):
|
||||
return {"name": self.env["res.partner"].browse(_id).name}
|
||||
|
||||
@restapi.method(
|
||||
[(["/<int:id>/get_name"], "GET")],
|
||||
output_param=restapi.CerberusValidator("_get_partner_schema"),
|
||||
auth="public",
|
||||
)
|
||||
def get_name(self, _id):
|
||||
return {"name": self.env["res.partner"].browse(_id).name}
|
||||
|
||||
@restapi.method(
|
||||
[(["/<int:id>/change_name"], "POST")],
|
||||
input_param=restapi.CerberusValidator("_get_partner_schema"),
|
||||
auth="user",
|
||||
)
|
||||
def update_name(self, _id, **params):
|
||||
pass
|
||||
|
||||
def _get_partner_schema(self):
|
||||
return {"name": {"type": "string", "required": True}}
|
||||
|
||||
self.assertFalse(self._get_controller_for(TestServiceNewApi))
|
||||
self._build_services(self, TestServiceNewApi)
|
||||
controller = self._get_controller_for(TestServiceNewApi)
|
||||
|
||||
routes = self._get_controller_route_methods(controller)
|
||||
self.assertSetEqual(
|
||||
set(routes.keys()),
|
||||
{"get_get", "get_get_name", "post_update_name"}
|
||||
| self._controller_route_method_names,
|
||||
)
|
||||
|
||||
method = routes["get_get"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["GET"],
|
||||
"auth": "public",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": [
|
||||
"/test_controller/partner/<int:id>/get",
|
||||
"/test_controller/partner/<int:id>",
|
||||
],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
method = routes["get_get_name"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["GET"],
|
||||
"auth": "public",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": ["/test_controller/partner/<int:id>/get_name"],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
method = routes["post_update_name"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["POST"],
|
||||
"auth": "user",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": ["/test_controller/partner/<int:id>/change_name"],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
def test_03(self):
|
||||
"""Check that the controller builder takes care of services inheritance"""
|
||||
|
||||
# pylint: disable=R7980
|
||||
class TestPartnerService(Component):
|
||||
_inherit = "base.rest.service"
|
||||
_name = "test.partner.service"
|
||||
_usage = "partner"
|
||||
_collection = self._collection_name
|
||||
_description = "test"
|
||||
|
||||
@restapi.method(
|
||||
[(["/<int:id>/get", "/<int:id>"], "GET")],
|
||||
output_param=restapi.CerberusValidator("_get_partner_schema"),
|
||||
auth="public",
|
||||
)
|
||||
def get(self, _id):
|
||||
return {"name": self.env["res.partner"].browse(_id).name}
|
||||
|
||||
def _get_partner_schema(self):
|
||||
return {"name": {"type": "string", "required": True}}
|
||||
|
||||
class TestInheritPartnerService(Component):
|
||||
_inherit = "test.partner.service"
|
||||
|
||||
@restapi.method(
|
||||
[(["/<int:id>/get_name"], "GET")],
|
||||
output_param=restapi.CerberusValidator("_get_partner_schema"),
|
||||
auth="public",
|
||||
)
|
||||
def get_name(self, _id):
|
||||
return {"name": self.env["res.partner"].browse(_id).name}
|
||||
|
||||
@restapi.method(
|
||||
[(["/<int:id>/change_name"], "POST")],
|
||||
input_param=restapi.CerberusValidator("_get_partner_schema"),
|
||||
auth="user",
|
||||
)
|
||||
def update_name(self, _id, **params):
|
||||
pass
|
||||
|
||||
self.assertFalse(self._get_controller_for(TestPartnerService))
|
||||
self._build_services(self, TestPartnerService, TestInheritPartnerService)
|
||||
controller = self._get_controller_for(TestPartnerService)
|
||||
|
||||
routes = self._get_controller_route_methods(controller)
|
||||
self.assertSetEqual(
|
||||
set(routes.keys()),
|
||||
{"get_get", "get_get_name", "post_update_name"}
|
||||
| self._controller_route_method_names,
|
||||
)
|
||||
|
||||
method = routes["get_get"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["GET"],
|
||||
"auth": "public",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": [
|
||||
"/test_controller/partner/<int:id>/get",
|
||||
"/test_controller/partner/<int:id>",
|
||||
],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
method = routes["get_get_name"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["GET"],
|
||||
"auth": "public",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": ["/test_controller/partner/<int:id>/get_name"],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
method = routes["post_update_name"]
|
||||
self.assertDictEqual(
|
||||
getattr(method, ROUTING_DECORATOR_ATTR),
|
||||
{
|
||||
"methods": ["POST"],
|
||||
"auth": "user",
|
||||
"cors": None,
|
||||
"csrf": False,
|
||||
"routes": ["/test_controller/partner/<int:id>/change_name"],
|
||||
"save_session": True,
|
||||
"type": "restapi",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class TestControllerBuilder2(TransactionRestServiceRegistryCase):
|
||||
"""Test Odoo controller builder
|
||||
|
||||
In this class we test the generation of odoo controllers from the services
|
||||
component
|
||||
|
||||
The test requires a fresh base controller
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self._setup_registry(self)
|
||||
|
||||
def tearDown(self):
|
||||
self._teardown_registry(self)
|
||||
super().tearDown()
|
||||
|
||||
def test_04(self):
|
||||
"""Test controller generated from services with new API methods and
|
||||
old api takes into account the _default_auth
|
||||
Routes directly defined on the RestConroller without auth should also
|
||||
use the _default_auth
|
||||
"""
|
||||
default_auth = "my_default_auth"
|
||||
default_cors = "*"
|
||||
default_csrf = True
|
||||
default_save_session = True
|
||||
self._BaseTestController._default_auth = default_auth
|
||||
self._BaseTestController._default_cors = default_cors
|
||||
self._BaseTestController._default_csrf = default_csrf
|
||||
self._BaseTestController._default_save_session = default_save_session
|
||||
|
||||
# pylint: disable=R7980
|
||||
class TestService(Component):
|
||||
_inherit = "base.rest.service"
|
||||
_name = "test.partner.service"
|
||||
_usage = "partner"
|
||||
_collection = self._collection_name
|
||||
_description = "test"
|
||||
|
||||
@restapi.method(
|
||||
[(["/new_api_method_with"], "GET")],
|
||||
auth="public",
|
||||
cors="http://my_site",
|
||||
csrf=not default_csrf,
|
||||
save_session=not default_save_session,
|
||||
)
|
||||
def new_api_method_with(self, _id):
|
||||
return {"name": self.env["res.partner"].browse(_id).name}
|
||||
|
||||
@restapi.method([(["/new_api_method_without"], "GET")])
|
||||
def new_api_method_without(self, _id):
|
||||
return {"name": self.env["res.partner"].browse(_id).name}
|
||||
|
||||
# OLD API method
|
||||
def get(self, _id, message):
|
||||
pass
|
||||
|
||||
# Validator
|
||||
def _validator_get(self):
|
||||
# no parameters by default
|
||||
return {}
|
||||
|
||||
self._build_services(self, TestService)
|
||||
controller = self._get_controller_for(TestService)
|
||||
|
||||
routes = self._get_controller_route_methods(controller)
|
||||
for attr, default in [
|
||||
("auth", default_auth),
|
||||
("cors", default_cors),
|
||||
("csrf", default_csrf),
|
||||
("save_session", default_save_session),
|
||||
]:
|
||||
self.assertEqual(
|
||||
getattr(routes["get_new_api_method_without"], ROUTING_DECORATOR_ATTR)[
|
||||
attr
|
||||
],
|
||||
default,
|
||||
"wrong %s" % attr,
|
||||
)
|
||||
self.assertEqual(
|
||||
getattr(routes["get_new_api_method_with"], ROUTING_DECORATOR_ATTR)["auth"],
|
||||
"public",
|
||||
)
|
||||
self.assertEqual(
|
||||
getattr(routes["get_new_api_method_with"], ROUTING_DECORATOR_ATTR)["cors"],
|
||||
"http://my_site",
|
||||
)
|
||||
self.assertEqual(
|
||||
getattr(routes["get_new_api_method_with"], ROUTING_DECORATOR_ATTR)["csrf"],
|
||||
not default_csrf,
|
||||
)
|
||||
self.assertEqual(
|
||||
getattr(routes["get_new_api_method_with"], ROUTING_DECORATOR_ATTR)[
|
||||
"save_session"
|
||||
],
|
||||
not default_save_session,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
getattr(routes["get_get"], ROUTING_DECORATOR_ATTR)["auth"],
|
||||
default_auth,
|
||||
"wrong auth for get_get",
|
||||
)
|
||||
|
||||
for attr, default in [
|
||||
("auth", default_auth),
|
||||
("cors", default_cors),
|
||||
("csrf", default_csrf),
|
||||
("save_session", default_save_session),
|
||||
]:
|
||||
self.assertEqual(
|
||||
getattr(routes["my_controller_route_without"], ROUTING_DECORATOR_ATTR)[
|
||||
attr
|
||||
],
|
||||
default,
|
||||
"wrong %s" % attr,
|
||||
)
|
||||
|
||||
routing = getattr(routes["my_controller_route_with"], ROUTING_DECORATOR_ATTR)
|
||||
for attr, value in [
|
||||
("auth", "public"),
|
||||
("cors", "http://with_cors"),
|
||||
("csrf", "False"),
|
||||
("save_session", "False"),
|
||||
]:
|
||||
|
||||
self.assertEqual(
|
||||
routing[attr],
|
||||
value,
|
||||
"wrong %s" % attr,
|
||||
)
|
||||
self.assertEqual(
|
||||
getattr(
|
||||
routes["my_controller_route_without_auth_2"], ROUTING_DECORATOR_ATTR
|
||||
)["auth"],
|
||||
None,
|
||||
"wrong auth for my_controller_route_without_auth_2",
|
||||
)
|
||||
|
||||
def test_05(self):
|
||||
"""Test auth="public_or_default" on restapi.method
|
||||
|
||||
The auth method on the route should be public_or_my_default_auth
|
||||
since the ir.http model provides the _auth_method_public_or_my_default_auth methods
|
||||
"""
|
||||
default_auth = "my_default_auth"
|
||||
self._BaseTestController._default_auth = default_auth
|
||||
|
||||
# pylint: disable=R7980
|
||||
class TestService(Component):
|
||||
_inherit = "base.rest.service"
|
||||
_name = "test.partner.service"
|
||||
_usage = "partner"
|
||||
_collection = self._collection_name
|
||||
_description = "test"
|
||||
|
||||
@restapi.method(
|
||||
[(["/new_api_method_with_public_or"], "GET")], auth="public_or_default"
|
||||
)
|
||||
def new_api_method_with_public_or(self, _id):
|
||||
return {"name": self.env["res.partner"].browse(_id).name}
|
||||
|
||||
# Validator
|
||||
def _validator_get(self):
|
||||
# no parameters by default
|
||||
return {}
|
||||
|
||||
# delare the auth méthod on ir.http
|
||||
with _add_method(
|
||||
self.env["ir.http"],
|
||||
"_auth_method_public_or_my_default_auth",
|
||||
lambda a: True,
|
||||
):
|
||||
self._build_services(self, TestService)
|
||||
|
||||
controller = self._get_controller_for(TestService)
|
||||
routes = self._get_controller_route_methods(controller)
|
||||
|
||||
self.assertEqual(
|
||||
getattr(
|
||||
routes["get_new_api_method_with_public_or"], ROUTING_DECORATOR_ATTR
|
||||
)["auth"],
|
||||
"public_or_my_default_auth",
|
||||
)
|
||||
|
||||
def test_06(self):
|
||||
"""Test auth="public_or_default" on restapi.method
|
||||
|
||||
The auth method on the route should be the default_auth configurerd on the controller
|
||||
since the ir.http model doesn't provides the _auth_method_public_or_my_default_auth
|
||||
methods
|
||||
"""
|
||||
default_auth = "my_default_auth"
|
||||
self._BaseTestController._default_auth = default_auth
|
||||
|
||||
# pylint: disable=R7980
|
||||
class TestService(Component):
|
||||
_inherit = "base.rest.service"
|
||||
_name = "test.partner.service"
|
||||
_usage = "partner"
|
||||
_collection = self._collection_name
|
||||
_description = "test"
|
||||
|
||||
@restapi.method(
|
||||
[(["/new_api_method_with_public_or"], "GET")], auth="public_or_default"
|
||||
)
|
||||
def new_api_method_with_public_or(self, _id):
|
||||
return {"name": self.env["res.partner"].browse(_id).name}
|
||||
|
||||
# Validator
|
||||
def _validator_get(self):
|
||||
# no parameters by default
|
||||
return {}
|
||||
|
||||
self._build_services(self, TestService)
|
||||
controller = self._get_controller_for(TestService)
|
||||
routes = self._get_controller_route_methods(controller)
|
||||
|
||||
self.assertEqual(
|
||||
getattr(
|
||||
routes["get_new_api_method_with_public_or"], ROUTING_DECORATOR_ATTR
|
||||
)["auth"],
|
||||
"my_default_auth",
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _add_method(obj, name, method):
|
||||
try:
|
||||
setattr(obj.__class__, name, method)
|
||||
yield
|
||||
finally:
|
||||
delattr(obj.__class__, name)
|
||||
|
|
@ -0,0 +1,338 @@
|
|||
# Copyright 2020 ACSONE SA/NV
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
|
||||
from .. import restapi
|
||||
from .common import TransactionRestServiceRegistryCase
|
||||
|
||||
|
||||
class TestOpenAPIGenerator(TransactionRestServiceRegistryCase):
|
||||
"""Test openapi document generation from REST services"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self._setup_registry(self)
|
||||
|
||||
def tearDown(self):
|
||||
self._teardown_registry(self)
|
||||
super().tearDown()
|
||||
|
||||
def test_01(self):
|
||||
"""Simple test case"""
|
||||
|
||||
# pylint: disable=R7980
|
||||
class PartnerService(Component):
|
||||
_inherit = "base.rest.service"
|
||||
_name = "test.partner.service"
|
||||
_usage = "partner"
|
||||
_collection = self._collection_name
|
||||
_description = "Sercice description"
|
||||
|
||||
@restapi.method(
|
||||
[(["/<int:id>/get", "/<int:id>"], "GET")],
|
||||
output_param=restapi.CerberusValidator("_get_partner_schema"),
|
||||
auth="public",
|
||||
)
|
||||
def get(self, _id):
|
||||
"""Get the partner information"""
|
||||
|
||||
def _get_partner_schema(self):
|
||||
return {"name": {"type": "string", "required": True}}
|
||||
|
||||
self._build_services(self, PartnerService)
|
||||
service = self._get_service_component(self, "partner")
|
||||
openapi = service.to_openapi()
|
||||
self.assertTrue(openapi)
|
||||
|
||||
# The service url is available at base_url/controller._root_path/_usage
|
||||
url = openapi["servers"][0]["url"]
|
||||
self.assertEqual(self.base_url + "/test_controller/partner", url)
|
||||
|
||||
# The title is generated from the service usage
|
||||
# The service info must contains a title and a description
|
||||
info = openapi["info"]
|
||||
self.assertEqual(info["title"], "%s REST services" % PartnerService._usage)
|
||||
self.assertEqual(info["description"], PartnerService._description)
|
||||
|
||||
paths = openapi["paths"]
|
||||
# The paths must contains 2 entries (1 by routes)
|
||||
self.assertSetEqual({"/{id}/get", "/{id}"}, set(openapi["paths"]))
|
||||
|
||||
for p in ["/{id}/get", "/{id}"]:
|
||||
path = paths[p]
|
||||
# The method for the paths is get
|
||||
self.assertIn("get", path)
|
||||
# The summary is the method docstring
|
||||
get = path["get"]
|
||||
self.assertEqual(get["summary"], "Get the partner information")
|
||||
# The reponse for status 200 is the openapi schema generated from
|
||||
# the cerberus schema returned by the _get_partner_schema method
|
||||
resp = None
|
||||
for item in get["responses"].items():
|
||||
if item[0] == "200":
|
||||
resp = item[1]
|
||||
self.assertTrue(resp)
|
||||
self.assertDictEqual(
|
||||
resp,
|
||||
{
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {"name": {"type": "string"}},
|
||||
"required": ["name"],
|
||||
"type": "object",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
# The path contains parameters
|
||||
self.assertDictEqual(
|
||||
path.get("parameters", [{}])[0],
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": True,
|
||||
"schema": {"type": "integer", "format": "int32"},
|
||||
},
|
||||
)
|
||||
|
||||
def test_02(self):
|
||||
"""Test path parameters
|
||||
|
||||
The new api allows you to define paths parameters. In this test
|
||||
we check that these parameters are into the openapi specification
|
||||
"""
|
||||
|
||||
# pylint: disable=R7980
|
||||
class PartnerService(Component):
|
||||
_inherit = "base.rest.service"
|
||||
_name = "test.partner.service"
|
||||
_usage = "partner"
|
||||
_collection = self._collection_name
|
||||
_description = "Sercice description"
|
||||
|
||||
@restapi.method(
|
||||
[(["/<int:id>/update_name/<string:name>"], "POST")], auth="public"
|
||||
)
|
||||
def update_name(self, _id, _name):
|
||||
"""update_name"""
|
||||
|
||||
self._build_services(self, PartnerService)
|
||||
service = self._get_service_component(self, "partner")
|
||||
openapi = service.to_openapi()
|
||||
self.assertTrue(openapi)
|
||||
paths = openapi["paths"]
|
||||
self.assertIn("/{id}/update_name/{name}", paths)
|
||||
path = paths["/{id}/update_name/{name}"]
|
||||
self.assertIn("post", path)
|
||||
parameters = path["parameters"]
|
||||
self.assertEqual(2, len(parameters))
|
||||
name_param = {}
|
||||
id_param = {}
|
||||
for p in parameters:
|
||||
if p["name"] == "id":
|
||||
id_param = p
|
||||
else:
|
||||
name_param = p
|
||||
self.assertDictEqual(
|
||||
name_param,
|
||||
{
|
||||
"in": "path",
|
||||
"name": "name",
|
||||
"required": True,
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
)
|
||||
self.assertDictEqual(
|
||||
id_param,
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": True,
|
||||
"schema": {"type": "integer", "format": "int32"},
|
||||
},
|
||||
)
|
||||
|
||||
# pylint: disable=W8110
|
||||
def test_03(self):
|
||||
"""Test default parameters and default responses
|
||||
|
||||
You can define default parameters and responses at service level.
|
||||
In this test we check that these parameters and responses are into the
|
||||
openapi specification
|
||||
"""
|
||||
default_params = [
|
||||
{
|
||||
"name": "API-KEY",
|
||||
"in": "header",
|
||||
"description": "API key for Authorization",
|
||||
"required": True,
|
||||
"schema": {"type": "string"},
|
||||
"style": "simple",
|
||||
}
|
||||
]
|
||||
|
||||
# pylint: disable=R7980
|
||||
class PartnerService(Component):
|
||||
_inherit = "base.rest.service"
|
||||
_name = "test.partner.service"
|
||||
_usage = "partner"
|
||||
_collection = self._collection_name
|
||||
_description = "Sercice description"
|
||||
|
||||
@restapi.method(
|
||||
[(["/<int:id>/update_name/<string:name>"], "POST")], auth="public"
|
||||
)
|
||||
def update_name(self, _id, _name):
|
||||
"""update_name"""
|
||||
|
||||
def _get_openapi_default_parameters(self):
|
||||
defaults = super()._get_openapi_default_parameters()
|
||||
defaults.extend(default_params)
|
||||
return defaults
|
||||
|
||||
def _get_openapi_default_responses(self):
|
||||
responses = super()._get_openapi_default_responses().copy()
|
||||
responses["999"] = "TEST"
|
||||
return responses
|
||||
|
||||
self._build_services(self, PartnerService)
|
||||
service = self._get_service_component(self, "partner")
|
||||
openapi = service.to_openapi()
|
||||
paths = openapi["paths"]
|
||||
self.assertIn("/{id}/update_name/{name}", paths)
|
||||
path = paths["/{id}/update_name/{name}"]
|
||||
self.assertIn("post", path)
|
||||
parameters = path["post"].get("parameters", [])
|
||||
self.assertListEqual(parameters, default_params)
|
||||
responses = path["post"].get("responses", [])
|
||||
self.assertIn("999", responses)
|
||||
|
||||
def test_04(self):
|
||||
"""Binary and Multipart form-data test case"""
|
||||
|
||||
# pylint: disable=R7980
|
||||
class AttachmentService(Component):
|
||||
_inherit = "base.rest.service"
|
||||
_name = "test.attachment.service"
|
||||
_usage = "attachment"
|
||||
_collection = self._collection_name
|
||||
_description = "Sercice description"
|
||||
|
||||
@restapi.method(
|
||||
routes=[(["/<int:id>/download"], "GET")],
|
||||
output_param=restapi.BinaryData(required=True),
|
||||
)
|
||||
def download(self, _id):
|
||||
"""download the attachment"""
|
||||
|
||||
@restapi.method(
|
||||
routes=[(["/create"], "POST")],
|
||||
input_param=restapi.MultipartFormData(
|
||||
{
|
||||
"file": restapi.BinaryData(
|
||||
mediatypes=["image/png", "image/jpeg"]
|
||||
),
|
||||
"params": restapi.CerberusValidator("_get_attachment_schema"),
|
||||
}
|
||||
),
|
||||
output_param=restapi.CerberusValidator("_get_attachment_schema"),
|
||||
)
|
||||
# pylint: disable=W8106
|
||||
def create(self, file, params):
|
||||
"""create the attachment"""
|
||||
|
||||
def _get_attachment_schema(self):
|
||||
return {"name": {"type": "string", "required": True}}
|
||||
|
||||
self._build_services(self, AttachmentService)
|
||||
service = self._get_service_component(self, "attachment")
|
||||
openapi = service.to_openapi()
|
||||
paths = openapi["paths"]
|
||||
# The paths must contains 2 entries (1 by routes)
|
||||
self.assertSetEqual({"/{id}/download", "/create"}, set(openapi["paths"]))
|
||||
path_download = paths["/{id}/download"]
|
||||
resp_download = None
|
||||
for item in path_download["get"]["responses"].items():
|
||||
if item[0] == "200":
|
||||
resp_download = item[1]
|
||||
self.assertTrue(resp_download)
|
||||
self.assertDictEqual(
|
||||
resp_download,
|
||||
{
|
||||
"content": {
|
||||
"*/*": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"required": True,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
# The path contains parameters
|
||||
self.assertDictEqual(
|
||||
path_download.get("parameters", [{}])[0],
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": True,
|
||||
"schema": {"type": "integer", "format": "int32"},
|
||||
},
|
||||
)
|
||||
path_create = paths["/create"]
|
||||
resp_create = None
|
||||
for item in path_create["post"]["responses"].items():
|
||||
if item[0] == "200":
|
||||
resp_create = item[1]
|
||||
self.assertTrue(resp_create)
|
||||
self.assertDictEqual(
|
||||
resp_create,
|
||||
{
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {"name": {"type": "string"}},
|
||||
"required": ["name"],
|
||||
"type": "object",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
request_create = path_create["post"]["requestBody"]
|
||||
self.assertDictEqual(
|
||||
request_create,
|
||||
{
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"required": False,
|
||||
},
|
||||
"params": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": ["name"],
|
||||
},
|
||||
},
|
||||
"encoding": {
|
||||
"file": {"contentType": "image/png, image/jpeg"}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
# Copyright 2021 ACSONE SA/NV
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
|
||||
from .. import restapi
|
||||
from .common import BaseRestCase, TransactionRestServiceRegistryCase
|
||||
|
||||
|
||||
class TestServiceContextProvider(TransactionRestServiceRegistryCase):
|
||||
"""Test Odoo service context provider
|
||||
|
||||
In this class we test the context provided by the service context provider
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self._setup_registry(self)
|
||||
|
||||
def tearDown(self):
|
||||
self._teardown_registry(self)
|
||||
super().tearDown()
|
||||
|
||||
def test_01(self):
|
||||
"""Test authenticated_partner_id
|
||||
|
||||
In this case we check that the default service context provider provides
|
||||
no authenticated_partner_id
|
||||
"""
|
||||
|
||||
# pylint: disable=R7980
|
||||
class TestServiceNewApi(Component):
|
||||
_inherit = "base.rest.service"
|
||||
_name = "test.partner.service"
|
||||
_usage = "partner"
|
||||
_collection = self._collection_name
|
||||
_description = "test"
|
||||
|
||||
@restapi.method(
|
||||
[(["/<int:id>/get", "/<int:id>"], "GET")],
|
||||
output_param=restapi.CerberusValidator("_get_partner_schema"),
|
||||
auth="public",
|
||||
)
|
||||
def get(self, _id):
|
||||
return {"name": self.env["res.partner"].browse(_id).name}
|
||||
|
||||
self._build_services(self, TestServiceNewApi)
|
||||
controller = self._get_controller_for(TestServiceNewApi)
|
||||
with MockRequest(self.env), controller().service_component(
|
||||
"partner"
|
||||
) as service:
|
||||
self.assertFalse(service.work.authenticated_partner_id)
|
||||
|
||||
def test_02(self):
|
||||
"""Test authenticated_partner_id
|
||||
|
||||
In this case we check that the 'abstract.user.authenticated.partner.provider'
|
||||
service context provider provides the current user's partner as
|
||||
authenticated_partner_id
|
||||
"""
|
||||
|
||||
# pylint: disable=R7880
|
||||
class TestComponentContextprovider(Component):
|
||||
_name = "test.component.context.provider"
|
||||
_inherit = [
|
||||
"abstract.user.authenticated.partner.provider",
|
||||
"base.rest.service.context.provider",
|
||||
]
|
||||
_usage = "test_component_context_provider"
|
||||
|
||||
self._BaseTestController._component_context_provider = (
|
||||
"test_component_context_provider"
|
||||
)
|
||||
|
||||
# pylint: disable=R7980
|
||||
class TestServiceNewApi(Component):
|
||||
_inherit = "base.rest.service"
|
||||
_name = "test.partner.service"
|
||||
_usage = "partner"
|
||||
_collection = self._collection_name
|
||||
_description = "test"
|
||||
|
||||
@restapi.method(
|
||||
[(["/<int:id>/get", "/<int:id>"], "GET")],
|
||||
output_param=restapi.CerberusValidator("_get_partner_schema"),
|
||||
auth="public",
|
||||
)
|
||||
def get(self, _id):
|
||||
return {"name": self.env["res.partner"].browse(_id).name}
|
||||
|
||||
self._build_components(TestComponentContextprovider)
|
||||
self._build_services(self, TestServiceNewApi)
|
||||
controller = self._get_controller_for(TestServiceNewApi)
|
||||
with MockRequest(self.env), controller().service_component(
|
||||
"partner"
|
||||
) as service:
|
||||
self.assertEqual(
|
||||
service.work.authenticated_partner_id, self.env.user.partner_id.id
|
||||
)
|
||||
|
||||
def test_03(self):
|
||||
"""Test authenticated_partner_id
|
||||
|
||||
In this case we check that redefining the method _get_authenticated_partner_id
|
||||
changes the authenticated_partner_id provided by the service context provider
|
||||
"""
|
||||
|
||||
# pylint: disable=R7880
|
||||
class TestComponentContextprovider(Component):
|
||||
_name = "test.component.context.provider"
|
||||
_inherit = "base.rest.service.context.provider"
|
||||
_usage = "test_component_context_provider"
|
||||
|
||||
def _get_authenticated_partner_id(self):
|
||||
return 9999
|
||||
|
||||
self._BaseTestController._component_context_provider = (
|
||||
"test_component_context_provider"
|
||||
)
|
||||
|
||||
# pylint: disable=R7980
|
||||
class TestServiceNewApi(Component):
|
||||
_inherit = "base.rest.service"
|
||||
_name = "test.partner.service"
|
||||
_usage = "partner"
|
||||
_collection = self._collection_name
|
||||
_description = "test"
|
||||
|
||||
@restapi.method(
|
||||
[(["/<int:id>/get", "/<int:id>"], "GET")],
|
||||
output_param=restapi.CerberusValidator("_get_partner_schema"),
|
||||
auth="public",
|
||||
)
|
||||
def get(self, _id):
|
||||
return {"name": self.env["res.partner"].browse(_id).name}
|
||||
|
||||
self._build_components(TestComponentContextprovider)
|
||||
self._build_services(self, TestServiceNewApi)
|
||||
controller = self._get_controller_for(TestServiceNewApi)
|
||||
with MockRequest(self.env), controller().service_component(
|
||||
"partner"
|
||||
) as service:
|
||||
self.assertEqual(service.work.authenticated_partner_id, 9999)
|
||||
|
||||
|
||||
class CommonCase(BaseRestCase):
|
||||
|
||||
# dummy test method to pass codecov
|
||||
def test_04(self):
|
||||
self.assertEqual(self.registry.test_cr, self.cr)
|
||||
Loading…
Add table
Add a link
Reference in a new issue