Skip to content

Commit

Permalink
mock_odoo_request: Preserve the order of given form data (#44)
Browse files Browse the repository at this point in the history
The input stream passed to the request as encoded form data must preserve the order
in case of an OrderedDict.
  • Loading branch information
ddufresne authored and numigi-robot committed Dec 2, 2019
1 parent 763a4b2 commit a174017
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 28 deletions.
102 changes: 74 additions & 28 deletions test_http_request/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
import json
import werkzeug
from contextlib import contextmanager
from io import BytesIO
from odoo.addons.http_routing.models.ir_http import url_for
from odoo.api import Environment
from odoo.http import HttpRequest, JsonRequest, OpenERPSession, _request_stack
from odoo.tools import config
from typing import Optional
from werkzeug.contrib.sessions import FilesystemSessionStore
from typing import Optional, Union
from werkzeug.contrib.sessions import FilesystemSessionStore, Session
from werkzeug.datastructures import ImmutableOrderedMultiDict
from werkzeug.test import EnvironBuilder
from werkzeug.urls import url_encode
from werkzeug.wrappers import Request


Expand Down Expand Up @@ -54,6 +57,21 @@ class _MockOdooJsonRequest(_MockOdooRequestMixin, JsonRequest):
pass


def _make_environ_form_data_stream(data: dict) -> BytesIO:
"""Make the form data stream for an Odoo http request.
Odoo uses ImmutableOrderedMultiDict to store url encoded form
data instead of the default ImmutableMultiDict in werkzeug.
The test utility :class:`werkzeug.test.EnvironBuilder` uses
MultiDict to store form data.
This must be ajusted in order reproduce properly the behavior of Odoo.
"""
encoded_data = url_encode(data).encode("ascii")
return BytesIO(encoded_data)


def _make_environ(
method: str = 'POST',
headers: Optional[dict] = None,
Expand All @@ -66,15 +84,60 @@ def _make_environ(
method=method,
data=json.dumps(data or {}) if routing_type == 'json' else data,
headers=headers,
)
environ_builder.content_type = (
'application/json' if routing_type == 'json' else
'application/x-www-form-urlencoded'
content_type=(
'application/json' if routing_type == 'json' else
'application/x-www-form-urlencoded'
)
)
environ = environ_builder.get_environ()

if routing_type == 'http' and data:
environ['wsgi.input'] = _make_environ_form_data_stream(data)

return environ


def _set_request_storage_class(httprequest: Request):
"""Set the data structure used to store form data.
This is done in the method Root.dispatch of odoo/http.py.
"""
httprequest.parameter_storage_class = ImmutableOrderedMultiDict


def _make_werkzeug_request(environ: dict) -> Request:
"""Make a werkzeug request from the given environ."""
httprequest = Request(environ)
_set_request_storage_class(httprequest)
return httprequest


def _make_filesystem_session(env: Environment) -> Session:
session_store = FilesystemSessionStore(
config.session_dir, session_class=OpenERPSession, renew_missing=True)
session = session_store.new()
session.db = env.cr.dbname
session.uid = env.uid
session.context = env.context
return session


def _make_odoo_request(
werkzeug_request: Request, env: Environment, routing_type: str,
) -> Union[_MockOdooHttpRequest, _MockOdooJsonRequest]:
"""Make an Odoo request from the given werkzeug request."""
odoo_request_cls = (
_MockOdooJsonRequest if routing_type == 'json' else
_MockOdooHttpRequest
)
odoo_request = odoo_request_cls(werkzeug_request)
odoo_request._env = env
odoo_request._cr = env.cr
odoo_request._uid = env.uid
odoo_request._context = env.context
return odoo_request


@contextmanager
def mock_odoo_request(
env: Environment,
Expand All @@ -99,26 +162,9 @@ def mock_odoo_request(
:param routing_type: whether to use an http (x-www-form-urlencoded) or json request.
"""
environ = _make_environ(method, headers, data, routing_type)
httprequest = Request(environ)

session_store = FilesystemSessionStore(
config.session_dir, session_class=OpenERPSession, renew_missing=True)
session = session_store.new()
session.db = env.cr.dbname
session.uid = env.uid
session.context = env.context

httprequest.session = session
werkzeug_request = _make_werkzeug_request(environ)
werkzeug_request.session = _make_filesystem_session(env)
odoo_request = _make_odoo_request(werkzeug_request, env, routing_type)

odoo_http_request_cls = (
_MockOdooJsonRequest if routing_type == 'json' else
_MockOdooHttpRequest
)
odoo_http_request = odoo_http_request_cls(httprequest)
odoo_http_request._env = env
odoo_http_request._cr = env.cr
odoo_http_request._uid = env.uid
odoo_http_request._context = env.context

with odoo_http_request:
yield odoo_http_request
with odoo_request:
yield odoo_request
6 changes: 6 additions & 0 deletions test_http_request/tests/test_mock_http_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from odoo.http import request
from odoo.tests import common
from werkzeug.datastructures import ImmutableMultiDict
from werkzeug.urls import url_encode
from ..common import mock_odoo_request


Expand Down Expand Up @@ -57,3 +58,8 @@ def test_if_json_routing__data_contained_in_request_data(self):
with mock_odoo_request(self.env, data=self.data, routing_type='json'):
assert not request.httprequest.form
assert request.httprequest.data == json_data

def test_form_data_propagated_with_correct_key_order(self):
data = OrderedDict([(str(i), str(i)) for i in range(10)])
with mock_odoo_request(self.env, data=data):
assert url_encode(request.httprequest.form) == url_encode(data)

0 comments on commit a174017

Please sign in to comment.