Skip to content

Commit 3105961

Browse files
authored
Merge pull request #3 from OpenMatchmaking/feature-sage-utils-tests
Tests for sage-utils library
2 parents d023bb0 + 6298302 commit 3105961

14 files changed

+503
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ __pycache__/
1515
env/
1616
build/
1717
develop-eggs/
18+
.pytest_cache/
1819
dist/
1920
downloads/
2021
eggs/

Dockerfile

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM openmatchmaking/docker-base-python-image:3.6
2+
RUN apt-get update && apt-get -y install make
3+
4+
COPY requirements-dev.txt /requirements-dev.txt
5+
RUN pip install -r requirements-dev.txt
6+
7+
COPY ./ /app
8+
WORKDIR /app

Makefile

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
flake:
2+
flake8 sage_utils tests
3+
4+
develop:
5+
python setup.py develop
6+
7+
test:
8+
py.test -q -s --cov sage_utils --cov-report term-missing --tb=native

docker-compose.dev.yml

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
version: "2"
2+
3+
networks:
4+
app-tier:
5+
driver: bridge
6+
7+
services:
8+
9+
app:
10+
build:
11+
context: .
12+
dockerfile: ./Dockerfile
13+
image: sage-utils-dev
14+
environment:
15+
- APP_HOST=0.0.0.0
16+
- APP_DEBUG=False
17+
- APP_SERVER=none
18+
- WAIT_FOR_RABBITMQ=30
19+
- AMQP_USERNAME=user
20+
- AMQP_PASSWORD=password
21+
- AMQP_HOST=rabbitmq
22+
- AMQP_VIRTUAL_HOST=/
23+
volumes:
24+
- ./:/app/
25+
depends_on:
26+
- rabbitmq
27+
networks:
28+
- app-tier
29+
tty: true
30+
31+
rabbitmq:
32+
image: bitnami/rabbitmq:latest
33+
ports:
34+
- 5672:5672
35+
- 15672:15672
36+
environment:
37+
- RABBITMQ_USERNAME=user
38+
- RABBITMQ_PASSWORD=password
39+
- RABBITMQ_VHOST=/
40+
networks:
41+
- app-tier

requirements-dev.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
flake8==3.5.0
2+
pytest==3.5.0
3+
pytest-asyncio==0.8.0
4+
pytest-cov==2.5.1
5+
pudb==2015.4.1

sage_utils/amqp/extension.py

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77

88
class AmqpExtension(BaseExtension):
9+
extension_name = 'amqp'
910
app_attribute = 'amqp'
1011
workers = []
1112
active_tasks = []
@@ -31,6 +32,7 @@ async def connect(self):
3132
async def init(self, loop):
3233
if not hasattr(self.app, 'extensions'):
3334
setattr(self.app, 'extensions', {})
35+
setattr(self.app, self.app_attribute, self)
3436
self.app.extensions[self.extension_name] = self
3537

3638
for worker in self.workers:

setup.cfg

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[wheel]
2+
universal = 1
3+
4+
[flake8]
5+
max-line-length = 100

tests/amqp/__init__.py

Whitespace-only changes.

tests/amqp/test_extension.py

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import asyncio
2+
3+
import pytest
4+
from sage_utils.amqp.extension import AmqpExtension
5+
from sage_utils.amqp.base import AmqpWorker
6+
7+
from tests.fixtures import Application, FakeConfig
8+
9+
10+
class CustomWorker(AmqpWorker):
11+
12+
async def run(self, *args, **kwargs):
13+
await self.connect()
14+
15+
16+
@pytest.mark.asyncio
17+
async def test_extension_without_workers(event_loop):
18+
app = Application()
19+
extension = AmqpExtension(app)
20+
21+
await extension.init(event_loop)
22+
assert 'extensions' in app.__dict__
23+
assert isinstance(app.extensions, dict)
24+
assert len(app.extensions.keys()) == 1
25+
assert extension.extension_name in app.extensions.keys()
26+
assert app.extensions[extension.extension_name] == extension
27+
assert len(extension.active_tasks) == 0
28+
29+
await asyncio.sleep(0.1)
30+
31+
await extension.deinit(event_loop)
32+
assert 'extensions' in app.__dict__
33+
assert extension.extension_name not in app.extensions.keys()
34+
assert getattr(app, AmqpExtension.app_attribute) is None
35+
assert len(extension.active_tasks) == 0
36+
37+
38+
@pytest.mark.asyncio
39+
async def test_extension_init_with_workers(event_loop):
40+
app = Application(config=FakeConfig())
41+
amqp_worker = CustomWorker(app)
42+
extension = AmqpExtension(app)
43+
extension.register_worker(amqp_worker)
44+
45+
await extension.init(event_loop)
46+
assert 'extensions' in app.__dict__
47+
assert isinstance(app.extensions, dict)
48+
assert len(app.extensions.keys()) == 1
49+
assert extension.extension_name in app.extensions.keys()
50+
assert app.extensions[extension.extension_name] == extension
51+
assert len(extension.active_tasks) == 1
52+
53+
await asyncio.sleep(0.1)
54+
55+
await extension.deinit(event_loop)
56+
assert 'extensions' in app.__dict__
57+
assert extension.extension_name not in app.extensions.keys()
58+
assert getattr(app, AmqpExtension.app_attribute) is None
59+
assert len(extension.active_tasks) == 1

tests/amqp/test_rpc_client.py

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import pytest
2+
from sage_utils.amqp.clients import RpcAmqpClient
3+
from sage_utils.amqp.extension import AmqpExtension
4+
from sage_utils.constants import VALIDATION_ERROR
5+
from sage_utils.wrappers import Response
6+
7+
from tests.fixtures import Application, FakeConfig, FakeRegisterMicroserviceWorker
8+
9+
10+
REQUEST_QUEUE = FakeRegisterMicroserviceWorker.QUEUE_NAME
11+
REQUEST_EXCHANGE = FakeRegisterMicroserviceWorker.REQUEST_EXCHANGE_NAME
12+
RESPONSE_EXCHANGE_NAME = FakeRegisterMicroserviceWorker.RESPONSE_EXCHANGE_NAME
13+
VALIDATION_ERROR_DECR = FakeRegisterMicroserviceWorker.ERROR_DESCRIPTION
14+
15+
16+
@pytest.mark.asyncio
17+
async def test_rpc_amqp_client_returns_ok(event_loop):
18+
app = Application(config=FakeConfig(), loop=event_loop)
19+
register_worker = FakeRegisterMicroserviceWorker(app)
20+
extension = AmqpExtension(app)
21+
extension.register_worker(register_worker)
22+
23+
await extension.init(event_loop)
24+
25+
client = RpcAmqpClient(
26+
app=app,
27+
routing_key=REQUEST_QUEUE,
28+
request_exchange=REQUEST_EXCHANGE,
29+
response_queue='',
30+
response_exchange=RESPONSE_EXCHANGE_NAME
31+
)
32+
response = await client.send(payload={'name': 'microservice', 'version': '1.0.0'})
33+
34+
assert Response.CONTENT_FIELD_NAME in response.keys()
35+
assert response[Response.CONTENT_FIELD_NAME] == 'OK'
36+
37+
assert Response.EVENT_FIELD_NAME in response.keys()
38+
assert response[Response.EVENT_FIELD_NAME] is None
39+
40+
await extension.deinit(event_loop)
41+
42+
43+
@pytest.mark.asyncio
44+
async def test_rpc_amqp_client_returns_an_error(event_loop):
45+
app = Application(config=FakeConfig(), loop=event_loop)
46+
register_worker = FakeRegisterMicroserviceWorker(app)
47+
extension = AmqpExtension(app)
48+
extension.register_worker(register_worker)
49+
50+
await extension.init(event_loop)
51+
52+
client = RpcAmqpClient(
53+
app=app,
54+
routing_key=REQUEST_QUEUE,
55+
request_exchange=REQUEST_EXCHANGE,
56+
response_queue='',
57+
response_exchange=RESPONSE_EXCHANGE_NAME
58+
)
59+
response = await client.send(payload={})
60+
61+
assert Response.ERROR_FIELD_NAME in response.keys()
62+
assert 'message' in response[Response.ERROR_FIELD_NAME].keys()
63+
assert response[Response.ERROR_FIELD_NAME]['message'] == VALIDATION_ERROR_DECR
64+
65+
assert 'type' in response[Response.ERROR_FIELD_NAME].keys()
66+
assert response[Response.ERROR_FIELD_NAME]['type'] == VALIDATION_ERROR
67+
68+
assert Response.EVENT_FIELD_NAME in response.keys()
69+
assert response[Response.EVENT_FIELD_NAME] is None
70+
71+
await extension.deinit(event_loop)

tests/amqp/test_worker.py

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from unittest import mock
2+
3+
import pytest
4+
from sage_utils.amqp.extension import AmqpExtension
5+
from sage_utils.amqp.workers import BaseRegisterWorker
6+
7+
from tests.fixtures import Application, FakeConfig, FakeRegisterMicroserviceWorker
8+
9+
10+
REQUEST_QUEUE = FakeRegisterMicroserviceWorker.QUEUE_NAME
11+
REQUEST_EXCHANGE = FakeRegisterMicroserviceWorker.REQUEST_EXCHANGE_NAME
12+
RESPONSE_EXCHANGE_NAME = FakeRegisterMicroserviceWorker.RESPONSE_EXCHANGE_NAME
13+
14+
15+
def valid_microservice_data(_app):
16+
return {'name': 'microservice', 'version': '1.0.0'}
17+
18+
19+
def invalid_microservice_data(_app):
20+
return {}
21+
22+
23+
def test_default_implementation_raises_an_error_when_getting_data():
24+
with pytest.raises(NotImplementedError):
25+
app = Application(config=FakeConfig())
26+
instance = BaseRegisterWorker(app)
27+
instance.get_microservice_data(app)
28+
29+
30+
@pytest.mark.asyncio
31+
async def test_microservice_has_been_registered_successfully(event_loop):
32+
app = Application(config=FakeConfig(), loop=event_loop)
33+
register_worker = FakeRegisterMicroserviceWorker(app)
34+
extension = AmqpExtension(app)
35+
extension.register_worker(register_worker)
36+
37+
await extension.init(event_loop)
38+
39+
instance = BaseRegisterWorker(app)
40+
instance.REQUEST_QUEUE_NAME = REQUEST_QUEUE
41+
instance.REQUEST_EXCHANGE_NAME = REQUEST_EXCHANGE
42+
instance.RESPONSE_EXCHANGE_NAME = RESPONSE_EXCHANGE_NAME
43+
44+
with mock.patch(
45+
target='sage_utils.amqp.workers.BaseRegisterWorker.get_microservice_data',
46+
side_effect=valid_microservice_data
47+
):
48+
result = await instance.run()
49+
assert result is None
50+
51+
await extension.deinit(event_loop)
52+
53+
54+
@pytest.mark.asyncio
55+
async def test_register_worker_raises_an_error_with_invalid_response(event_loop):
56+
app = Application(config=FakeConfig(), loop=event_loop)
57+
register_worker = FakeRegisterMicroserviceWorker(app)
58+
extension = AmqpExtension(app)
59+
extension.register_worker(register_worker)
60+
61+
await extension.init(event_loop)
62+
63+
instance = BaseRegisterWorker(app)
64+
instance.REQUEST_QUEUE_NAME = REQUEST_QUEUE
65+
instance.REQUEST_EXCHANGE_NAME = REQUEST_EXCHANGE
66+
instance.RESPONSE_EXCHANGE_NAME = RESPONSE_EXCHANGE_NAME
67+
instance.RETRY_TIMEOUT = 0.1
68+
instance.MAX_RETRIES = 1
69+
70+
with mock.patch(
71+
target='sage_utils.amqp.workers.BaseRegisterWorker.get_microservice_data',
72+
side_effect=invalid_microservice_data
73+
):
74+
with pytest.raises(ConnectionError):
75+
await instance.run()
76+
77+
await extension.deinit(event_loop)
78+
79+
80+
@pytest.mark.asyncio
81+
async def test_register_worker_raises_an_error(event_loop):
82+
app = Application(config=FakeConfig(), loop=event_loop)
83+
register_worker = FakeRegisterMicroserviceWorker(app)
84+
extension = AmqpExtension(app)
85+
extension.register_worker(register_worker)
86+
87+
await extension.init(event_loop)
88+
89+
instance = BaseRegisterWorker(app)
90+
instance.REQUEST_QUEUE_NAME = 'invalid.queue.name'
91+
instance.REQUEST_EXCHANGE_NAME = 'some.request.exchange'
92+
instance.RESPONSE_EXCHANGE_NAME = 'some.response.exchange'
93+
instance.RETRY_TIMEOUT = 0.1
94+
instance.MAX_RETRIES = 1
95+
96+
with mock.patch(
97+
target='sage_utils.amqp.workers.BaseRegisterWorker.get_microservice_data',
98+
side_effect=valid_microservice_data
99+
):
100+
with pytest.raises(ConnectionError):
101+
await instance.run()
102+
103+
await extension.deinit(event_loop)

0 commit comments

Comments
 (0)