Skip to content

Commit

Permalink
Fix Mock create response
Browse files Browse the repository at this point in the history
  • Loading branch information
alfred82santa committed Sep 29, 2021
1 parent 8905a46 commit 5708063
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 27 deletions.
12 changes: 9 additions & 3 deletions Makefile
Expand Up @@ -34,13 +34,19 @@ run-tests:
@echo "Running tests..."
nosetests --with-coverage -d --cover-package=${PACKAGE_COVERAGE} --cover-erase

publish:
build:
python setup.py bdist_wheel

publish: clean build
@echo "Publishing new version on Pypi..."
python setup.py sdist upload
twine upload dist/*

clean:
@echo "Cleaning compiled files..."
find . | grep -E "(__pycache__|\.pyc|\.pyo)$ " | xargs rm -rf
rm -rf dist
rm -rf *.egg-info
rm -rf build

flake:
@echo "Running flake8 tests..."
Expand All @@ -52,4 +58,4 @@ autopep:

prepush: flake run-tests

pull-request: flake run-tests
pull-request: flake run-tests
13 changes: 10 additions & 3 deletions README.rst
Expand Up @@ -127,6 +127,13 @@ Changelog
=========


v0.7.2
------

- Fix Mock and Loggers plugins work together.
- Added RawDataMock in order to allow to set mock data as string or byte-string directly on spec.
- Added JsonDataMock in order to allow to set mock json data as a dictionary or list on spec.

v0.7.1
------

Expand All @@ -145,8 +152,8 @@ v0.6.1

- Tests improved.

- Added new exceptions: :class:`~service_client.plugins.TooManyRequestsPendingError` and
:class:`~service_client.plugins.TooMuchTimePendingError`.
- Added new exceptions: `service_client.plugins.TooManyRequestsPendingError` and
`service_client.plugins.TooMuchTimePendingError`.

- Added decorator in order to help to build service clients. It allows to define a method using a request model
but to call it using keywords to build request model which will be used to call method.
Expand All @@ -172,7 +179,7 @@ v0.6.0
------

- Improved Pool plugin. It now allows to set hard limit of pending requests, if it reach limit requests will
fail raising :class:`RequestLimitError`. In same way, it allows to set a timeout, in seconds, for pending requests and
fail raising `RequestLimitError`. In same way, it allows to set a timeout, in seconds, for pending requests and
it will raise same exception.

- Added new RateLimit plugin. It is similar to Pool plugin but using a period parameter, in seconds, in order to limit number
Expand Down
2 changes: 1 addition & 1 deletion service_client/__init__.py
Expand Up @@ -14,7 +14,7 @@

from .utils import ObjectWrapper

__version__ = '0.7.1'
__version__ = '0.7.2'


class ServiceClient:
Expand Down
51 changes: 38 additions & 13 deletions service_client/mocks.py
@@ -1,9 +1,10 @@
import json
from asyncio import get_event_loop
from asyncio.futures import Future
from collections import Mapping
from functools import wraps

from aiohttp import RequestInfo
from aiohttp.client_reqrep import ClientResponse
from aiohttp.helpers import TimerContext
from dirty_loader import LoaderNamespaceReversedCached
from multidict import CIMultiDict, CIMultiDictProxy
Expand Down Expand Up @@ -164,6 +165,7 @@ def _create_mock(self, endpoint_desc, session, request_params, mock_desc, loop):
return self.loader.factory(mock_desc.get('mock_type'),
endpoint_desc, session,
request_params, mock_desc,
service_client=self.service_client,
loop=loop)

async def prepare_session(self, endpoint_desc, session, request_params):
Expand All @@ -183,11 +185,12 @@ async def prepare_session(self, endpoint_desc, session, request_params):
class BaseMock:

def __init__(self, endpoint_desc, session, request_params,
mock_desc, loop=None):
mock_desc, service_client, loop=None):
self.endpoint_desc = endpoint_desc
self.session = session
self.request_params = request_params
self.mock_desc = mock_desc
self.service_client = service_client
self.loop = loop or get_event_loop()

async def __call__(self, *args, **kwargs):
Expand Down Expand Up @@ -218,17 +221,17 @@ async def writer(*args, **kwargs):
continue100 = Future()
continue100.set_result(False)

self.response = ClientResponse(method,
URL(url),
writer=create_task(writer()),
continue100=continue100,
timer=TimerContext(loop=self.loop),
request_info=RequestInfo(URL(url),
method,
kwargs.get('headers', [])),
traces=[],
loop=self.loop,
session=self.session)
self.response = self.service_client.create_response(method,
URL(url),
writer=create_task(writer()),
continue100=continue100,
timer=TimerContext(loop=self.loop),
request_info=RequestInfo(URL(url),
method,
kwargs.get('headers', [])),
traces=[],
loop=self.loop,
session=self.session)

self.response.status = self.mock_desc.get('status', 200)
self.response._headers = CIMultiDictProxy(CIMultiDict(self.mock_desc.get('headers', {})))
Expand All @@ -248,3 +251,25 @@ class RawFileMock(BaseFileMock):

def load_file(self, filename):
return open(filename, "rb").read()


class RawDataMock(BaseMock):
async def prepare_response(self):
data = self.mock_desc['data']
if isinstance(data, str):
data = data.encode()
elif not isinstance(data, bytes):
raise ValueError(data)
self.response._body = data


class JsonDataMock(BaseMock):
async def prepare_response(self):
data = self.mock_desc['data']
encoder = self.mock_desc.get('json_encoder', None)

if isinstance(data, (dict, Mapping, list)):
data = json.dumps(data, cls=encoder).encode()
else:
raise ValueError(data)
self.response._body = data
7 changes: 5 additions & 2 deletions setup.py
@@ -1,6 +1,6 @@
import ast

import os

from setuptools import setup

path = os.path.join(os.path.dirname(__file__), 'service_client', '__init__.py')
Expand Down Expand Up @@ -40,14 +40,17 @@
classifiers=[
'Intended Audience :: Developers',
'Programming Language :: Python',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
'Development Status :: 4 - Beta'],
packages=['service_client'],
include_package_data=False,
install_requires=['dirty-loader>=0.2.2', 'aiohttp>=3.7.4', 'configure-fork'],
description="Service Client Framework powered by Python asyncio.",
long_description_content_type='text/x-rst',
long_description=open(os.path.join(os.path.dirname(__file__), 'README.rst')).read(),
test_suite="nose.collector",
tests_require="nose",
Expand Down
60 changes: 55 additions & 5 deletions tests/tests_mocks.py
@@ -1,10 +1,11 @@
import os
from aiohttp import hdrs

from aiohttp import ClientResponse, hdrs
from aiohttp.client import ClientSession
from asynctest.case import TestCase
from multidict import CIMultiDict

from service_client.mocks import Mock, mock_manager, RawFileMock
from service_client.mocks import Mock, RawDataMock, RawFileMock, mock_manager
from service_client.utils import ObjectWrapper

MOCKS_DIR = os.path.join(os.path.dirname(__file__), 'mock_files')
Expand All @@ -31,9 +32,13 @@ async def setUp(self):
'file': 'data/mocks/opengate_v6/alarm/alarm_list.json'},
'endpoint': 'test_endpoint'}

self.service_client = type('DynTestServiceClient', (),
{'name': 'test_service_name',
'loop': self.loop})()
self.service_client = type(
'DynTestServiceClient',
(),
{'name': 'test_service_name',
'create_response': lambda self, *args, **kwargs: ObjectWrapper(ClientResponse(*args, **kwargs)),
'loop': self.loop}
)()
self.plugin.assign_service_client(self.service_client)

async def test_calling_mock(self):
Expand Down Expand Up @@ -132,3 +137,48 @@ async def test_patch_mock(self):
response = await self.session.request('POST', 'default_url')
self.assertEqual(200, response.status)
self.assertEqual('test data', (await response.text()))

@mock_manager.patch_mock_desc(patch={'mock_type': 'default:RawDataMock',
'data': b'binary-data'})
async def test_patch_mock_raw_data_bytes(self):
await self.plugin.prepare_session(self.service_desc, self.session, {})
self.assertIsInstance(self.session.request, RawDataMock)
response = await self.session.request('POST', 'default_url')
self.assertEqual(200, response.status)
self.assertEqual(b'binary-data', (await response.read()))

@mock_manager.patch_mock_desc(patch={'mock_type': 'default:RawDataMock',
'data': 'text-data'})
async def test_patch_mock_raw_data_string(self):
await self.plugin.prepare_session(self.service_desc, self.session, {})
response = await self.session.request('POST', 'default_url')
self.assertEqual(b'text-data', (await response.read()))

@mock_manager.patch_mock_desc(patch={'mock_type': 'default:RawDataMock',
'data': {}})
async def test_patch_mock_raw_data_fail(self):
with self.assertRaises(ValueError):
await self.plugin.prepare_session(self.service_desc, self.session, {})
await self.session.request('POST', 'default_url')

@mock_manager.patch_mock_desc(patch={'mock_type': 'default:JsonDataMock',
'data': {'key': 'value'}})
async def test_patch_mock_json_dict(self):
await self.plugin.prepare_session(self.service_desc, self.session, {})
response = await self.session.request('POST', 'default_url')
self.assertEqual(200, response.status)
self.assertEqual(b'{"key": "value"}', (await response.read()))

@mock_manager.patch_mock_desc(patch={'mock_type': 'default:JsonDataMock',
'data': ['value_1', 2]})
async def test_patch_mock_json_list(self):
await self.plugin.prepare_session(self.service_desc, self.session, {})
response = await self.session.request('POST', 'default_url')
self.assertEqual(b'["value_1", 2]', (await response.read()))

@mock_manager.patch_mock_desc(patch={'mock_type': 'default:JsonDataMock',
'data': 'fail'})
async def test_patch_mock_json_fail(self):
with self.assertRaises(ValueError):
await self.plugin.prepare_session(self.service_desc, self.session, {})
await self.session.request('POST', 'default_url')

0 comments on commit 5708063

Please sign in to comment.