[ADD] core: block requests calls during tests
Non-external tests should not be performing requests to external websites or services. Add a mock to handle such requests: - if the test is not tagged external - and the request is not to localhost - and the request is not to `file:` (it's possible to install a FileAdapter in a session to resolve file URLs) - raise a connection error (and log, both with some details of the blocked request for debugging) The mock is layered on the lowest possible level (`Session.send`), so tests can either: - reconfigure the mock to handle cases differently (the mock is re-created on every test) - layer their own mock at a higher level of the library Eventually we might also built-in a routing / dispatch mechanism so it's easier to declare external services you want to mock, somewhat similar to what's available client-side. Whitelist `file:` because e.g. zeep performs `file:` request, using a bespoke adapter installed in its session. Part-of: odoo/odoo#128497
This commit is contained in:
+29
-1
@@ -43,6 +43,7 @@ from xmlrpc import client as xmlrpclib
|
||||
import requests
|
||||
import werkzeug.urls
|
||||
from lxml import etree, html
|
||||
from requests import PreparedRequest, Session
|
||||
|
||||
import odoo
|
||||
from odoo import api
|
||||
@@ -243,7 +244,7 @@ def _normalize_arch_for_assert(arch_string, parser_method="xml"):
|
||||
arch_string = etree.fromstring(arch_string, parser=parser)
|
||||
return etree.tostring(arch_string, pretty_print=True, encoding='unicode')
|
||||
|
||||
|
||||
_super_send = requests.Session.send
|
||||
class BaseCase(case.TestCase, metaclass=MetaCase):
|
||||
""" Subclass of TestCase for Odoo-specific code. This class is abstract and
|
||||
expects self.registry, self.cr and self.uid to be initialized by subclasses.
|
||||
@@ -257,6 +258,33 @@ class BaseCase(case.TestCase, metaclass=MetaCase):
|
||||
super().__init__(methodName)
|
||||
self.addTypeEqualityFunc(etree._Element, self.assertTreesEqual)
|
||||
self.addTypeEqualityFunc(html.HtmlElement, self.assertTreesEqual)
|
||||
self.request_mock = None
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
if 'external' not in self.test_tags:
|
||||
# if the method is passed directly `patch` discards the session
|
||||
# object which we need
|
||||
# pylint: disable=unnecessary-lambda
|
||||
self.patch(
|
||||
requests.sessions.Session,
|
||||
'send',
|
||||
lambda s, r, **kwargs: self._request_handler(s, r, **kwargs),
|
||||
)
|
||||
|
||||
def _request_handler(self, s: Session, r: PreparedRequest, /, **kw):
|
||||
# allow localhost requests
|
||||
# TODO: also check port?
|
||||
url = werkzeug.urls.url_parse(r.url)
|
||||
if url.host in (HOST, 'localhost'):
|
||||
return _super_send(s, r, **kw)
|
||||
if url.scheme == 'file':
|
||||
return _super_send(s, r, **kw)
|
||||
|
||||
_logger.getChild('requests').info(
|
||||
"Blocking un-mocked external HTTP request %s %s", r.method, r.url)
|
||||
raise requests.exceptions.ConnectionError(
|
||||
f"External requests verboten (was {r.method} {r.url})")
|
||||
|
||||
def run(self, result):
|
||||
testMethod = getattr(self, self._testMethodName)
|
||||
|
||||
Reference in New Issue
Block a user