From b8a9ce81d435a552caa7ee1640eaaea4e7eb108a Mon Sep 17 00:00:00 2001 From: Valeryi Savich Date: Sat, 26 May 2018 14:51:16 +0300 Subject: [PATCH 1/9] Updated docker-compose.dev.yml --- docker-compose.dev.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index fac689b..b9e0735 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -47,7 +47,7 @@ services: - MONGODB_USERNAME=user - MONGODB_PASSWORD=password - MONGODB_HOST=mongodb - - MONGODB_DATABASE=auth + - MONGODB_DATABASE=game_servers_pool - WAIT_FOR_MONGODB=30 - WAIT_FOR_REDIS=30 - WAIT_FOR_RABBITMQ=30 @@ -80,7 +80,7 @@ services: environment: - MONGODB_USERNAME=user - MONGODB_PASSWORD=password - - MONGODB_DATABASE=auth + - MONGODB_DATABASE=game_servers_pool - MONGODB_ROOT_PASSWORD=root networks: - app-tier From 11432901a8de5979d4af37f38385ed0caea660c2 Mon Sep 17 00:00:00 2001 From: Valeryi Savich Date: Sat, 26 May 2018 14:51:35 +0300 Subject: [PATCH 2/9] Updated sage-utils to the latest release --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fc10c3f..04d4386 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ aioamqp==0.10.0 umongo==0.15.0 motor==1.2.1 marshmallow==2.15.0 -sage-utils==0.5.1 +sage-utils==0.5.4 pytest==3.4.0 pytest-cov==2.5.1 From 175a2224d66c92e5e765657ac25b6a2f0ab7eb10 Mon Sep 17 00:00:00 2001 From: Valeryi Savich Date: Sat, 26 May 2018 14:52:13 +0300 Subject: [PATCH 3/9] Added health check API endpoint and AMQP workers --- game-servers-pool/app/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/game-servers-pool/app/__init__.py b/game-servers-pool/app/__init__.py index 723dd7f..bb192eb 100644 --- a/game-servers-pool/app/__init__.py +++ b/game-servers-pool/app/__init__.py @@ -3,7 +3,7 @@ from sanic_mongodb_ext import MongoDbExtension from sanic_amqp_ext import AmqpExtension -from app.workers import GetServerWorker, MicroserviceRegisterWorker, RegisterServerWorker +from app.workers import GetServerWorker, RegisterServerWorker app = Sanic('microservice-auth') @@ -16,7 +16,6 @@ # RabbitMQ workers app.amqp.register_worker(GetServerWorker(app)) -app.amqp.register_worker(MicroserviceRegisterWorker(app)) app.amqp.register_worker(RegisterServerWorker(app)) From 7f291b41ac48738dbadf8ca036764032046cdb4e Mon Sep 17 00:00:00 2001 From: Valeryi Savich Date: Sat, 26 May 2018 14:56:17 +0300 Subject: [PATCH 4/9] Added running microservice register worker before starting app --- game-servers-pool/app/commands/run_server.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/game-servers-pool/app/commands/run_server.py b/game-servers-pool/app/commands/run_server.py index 1b84d7a..94899e2 100644 --- a/game-servers-pool/app/commands/run_server.py +++ b/game-servers-pool/app/commands/run_server.py @@ -1,6 +1,8 @@ +import asyncio from sanic_script import Command, Option from app import app +from app.workers.microservice_register import MicroserviceRegisterWorker class RunServerCommand(Command): @@ -14,7 +16,15 @@ class RunServerCommand(Command): Option('--port', '-p', dest='port'), ) + def register_microservice(self): + loop = asyncio.get_event_loop() + worker = MicroserviceRegisterWorker(self.app) + loop.run_until_complete(worker.run(loop=loop)) + loop.stop() + loop.close() + def run(self, *args, **kwargs): + self.register_microservice() self.app.run( host=kwargs.get('host', None) or self.app.config["APP_HOST"], port=kwargs.get('port', None) or self.app.config["APP_PORT"], From d7d275ace713bda5a190dbe13efd48c6d62be0b4 Mon Sep 17 00:00:00 2001 From: Valeryi Savich Date: Sat, 26 May 2018 14:56:43 +0300 Subject: [PATCH 5/9] Added validators for GameServer document --- game-servers-pool/app/game_servers/schemas.py | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/game-servers-pool/app/game_servers/schemas.py b/game-servers-pool/app/game_servers/schemas.py index 61946fe..73038f9 100644 --- a/game-servers-pool/app/game_servers/schemas.py +++ b/game-servers-pool/app/game_servers/schemas.py @@ -1,4 +1,5 @@ -from marshmallow import Schema, fields, validate +from bson import ObjectId +from marshmallow import Schema, fields, validate, validates, ValidationError from app import app @@ -23,12 +24,35 @@ class RequestGetServerSchema(Schema): ) -class GameServerSchema(GameServer.schema.as_marshmallow_schema()): +class RegisterGameServerSchema(GameServer.schema.as_marshmallow_schema()): + id = fields.String(required=False) + + @validates('id') + def validate_id(self, value): + if not ObjectId.is_valid(value): + raise ValidationError( + "'{}' is not a valid ObjectId, it must be a 12-byte " + "input or a 24-character hex string.".format(value) + ) + + class Meta: + model = GameServer + fields = ( + 'id', + 'host', + 'port', + 'available_slots', + 'credentials', + 'game_mode', + ) + + +class RetrieveGameServerSchema(GameServer.schema.as_marshmallow_schema()): class Meta: model = GameServer fields = ( 'host', 'port', - 'credentials' + 'credentials', ) From 9db2e264e692aefb2e0d0adf636ce24d3ae4f65b Mon Sep 17 00:00:00 2001 From: Valeryi Savich Date: Sat, 26 May 2018 15:01:32 +0300 Subject: [PATCH 6/9] Added logic for getting a random available server --- game-servers-pool/app/workers/get_server.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/game-servers-pool/app/workers/get_server.py b/game-servers-pool/app/workers/get_server.py index 0203b99..ddea4a5 100644 --- a/game-servers-pool/app/workers/get_server.py +++ b/game-servers-pool/app/workers/get_server.py @@ -16,9 +16,9 @@ class GetServerWorker(AmqpWorker): def __init__(self, app, *args, **kwargs): super(GetServerWorker, self).__init__(app, *args, **kwargs) from app.game_servers.documents import GameServer - from app.game_servers.schemas import RequestGetServerSchema, GameServerSchema + from app.game_servers.schemas import RequestGetServerSchema, RetrieveGameServerSchema self.game_server_document = GameServer - self.schema = GameServerSchema + self.schema = RetrieveGameServerSchema self.request_schema = RequestGetServerSchema async def validate_data(self, raw_data): @@ -46,12 +46,17 @@ async def get_game_server(self, raw_data): {'available_slots': {'$gte': data['required_slots']}}, {"game_mode": data['game_mode']} ] - }} + }}, + {'$sample': {'size': 1}} ] - instance = await self.game_server_document.collection.aggregate(pipeline).limit(1) - - serializer = self.schema() - serialized_instance = serializer.dump(instance).data + result = await self.game_server_document.collection.aggregate(pipeline).to_list(1) + + if result: + instance = result[0] + serializer = self.schema() + serialized_instance = serializer.dump(instance).data + else: + serialized_instance = None return Response.with_content(serialized_instance) async def process_request(self, channel, body, envelope, properties): From 0ab94564c31897495c08b63b2254c807928dc898 Mon Sep 17 00:00:00 2001 From: Valeryi Savich Date: Sat, 26 May 2018 15:07:03 +0300 Subject: [PATCH 7/9] Fixed filtering by ObjectId for GameServer document --- game-servers-pool/app/workers/register_server.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/game-servers-pool/app/workers/register_server.py b/game-servers-pool/app/workers/register_server.py index b91e2ac..e5232d1 100644 --- a/game-servers-pool/app/workers/register_server.py +++ b/game-servers-pool/app/workers/register_server.py @@ -1,8 +1,7 @@ import json -from uuid import uuid4 - from aioamqp import AmqpClosedConnection +from bson import ObjectId from marshmallow import ValidationError from sanic_amqp_ext import AmqpWorker from sage_utils.constants import VALIDATION_ERROR @@ -18,15 +17,15 @@ class RegisterServerWorker(AmqpWorker): def __init__(self, app, *args, **kwargs): super(RegisterServerWorker, self).__init__(app, *args, **kwargs) from app.game_servers.documents import GameServer + from app.game_servers.schemas import RegisterGameServerSchema self.game_server_document = GameServer - self.schema = GameServer.schema.as_marshmallow_schema() + self.schema = RegisterGameServerSchema async def validate_data(self, raw_data): try: data = json.loads(raw_data.strip()) except json.decoder.JSONDecodeError: data = {} - deserializer = self.schema() result = deserializer.load(data) if result.errors: @@ -40,12 +39,12 @@ async def register_game_server(self, raw_data): except ValidationError as exc: return Response.from_error(VALIDATION_ERROR, exc.normalized_messages()) - game_server_id = data.get('id', str(uuid4())) + object_id = ObjectId(data['id']) if 'id' in data.keys() else ObjectId() await self.game_server_document.collection.replace_one( - {'_id': game_server_id}, replacement=data, upsert=True + {'_id': object_id}, replacement=data, upsert=True ) - return Response.with_content({'id': game_server_id}) + return Response.with_content({'id': str(object_id)}) async def process_request(self, channel, body, envelope, properties): response = await self.register_game_server(body) From 4ffcd58dec7d0b357b0f7e574e9775da094f1006 Mon Sep 17 00:00:00 2001 From: Valeryi Savich Date: Sat, 26 May 2018 15:07:17 +0300 Subject: [PATCH 8/9] Implemented tests for GetServerWorker --- game-servers-pool/tests/test_get_server.py | 209 +++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 game-servers-pool/tests/test_get_server.py diff --git a/game-servers-pool/tests/test_get_server.py b/game-servers-pool/tests/test_get_server.py new file mode 100644 index 0000000..f258575 --- /dev/null +++ b/game-servers-pool/tests/test_get_server.py @@ -0,0 +1,209 @@ +import pytest +from sage_utils.amqp.clients import RpcAmqpClient +from sage_utils.constants import VALIDATION_ERROR +from sage_utils.wrappers import Response + +from app.game_servers.documents import GameServer +from app.workers.get_server import GetServerWorker + + +REQUEST_QUEUE = GetServerWorker.QUEUE_NAME +REQUEST_EXCHANGE = GetServerWorker.REQUEST_EXCHANGE_NAME +RESPONSE_EXCHANGE = GetServerWorker.RESPONSE_EXCHANGE_NAME + + +async def create_game_servers(init_data_list): + objects = [] + for create_data in init_data_list: + game_server = GameServer(**create_data) + await game_server.commit() + objects.append(game_server) + return objects + + +@pytest.mark.asyncio +async def test_worker_returns_one_existing_server_for_one_server_in_list(sanic_server): + await GameServer.collection.delete_many({}) + + create_data = { + 'host': '127.0.0.1', + 'port': 9000, + 'available_slots': 100, + 'credentials': { + 'token': 'super_secret_token' + }, + 'game_mode': '1v1' + } + objects = await create_game_servers([create_data, ]) + + client = RpcAmqpClient( + sanic_server.app, + routing_key=REQUEST_QUEUE, + request_exchange=REQUEST_EXCHANGE, + response_queue='', + response_exchange=RESPONSE_EXCHANGE + ) + response = await client.send(payload={ + 'required_slots': 20, + 'game_mode': "1v1" + }) + + assert Response.EVENT_FIELD_NAME in response.keys() + assert Response.CONTENT_FIELD_NAME in response.keys() + content = response[Response.CONTENT_FIELD_NAME] + + assert len(list(content.keys())) == 3 + assert set(content.keys()) == {'host', 'port', 'credentials'} + + assert content['host'] == objects[0].host + assert content['port'] == objects[0].port + assert content['credentials'] == objects[0].credentials + + await GameServer.collection.delete_many({}) + + +@pytest.mark.asyncio +async def test_worker_returns_a_random_server_from_a_list(sanic_server): + await GameServer.collection.delete_many({}) + + objects = await create_game_servers([ + { + 'host': '127.0.0.1', + 'port': 9000, + 'available_slots': 100, + 'credentials': { + 'token': 'super_secret_token' + }, + 'game_mode': 'team-deathmatch' + }, + { + 'host': '127.0.0.1', + 'port': 9001, + 'available_slots': 50, + 'credentials': { + 'token': 'super_secret_token2' + }, + 'game_mode': 'team-deathmatch' + }, + { + 'host': '127.0.0.1', + 'port': 9002, + 'available_slots': 10, + 'credentials': { + 'token': 'super_secret_token3' + }, + 'game_mode': 'team-deathmatch' + }, + ]) + + client = RpcAmqpClient( + sanic_server.app, + routing_key=REQUEST_QUEUE, + request_exchange=REQUEST_EXCHANGE, + response_queue='', + response_exchange=RESPONSE_EXCHANGE + ) + response = await client.send(payload={ + 'required_slots': 10, + 'game_mode': 'team-deathmatch' + }) + + assert Response.EVENT_FIELD_NAME in response.keys() + assert Response.CONTENT_FIELD_NAME in response.keys() + content = response[Response.CONTENT_FIELD_NAME] + + assert len(list(content.keys())) == 3 + assert set(content.keys()) == {'host', 'port', 'credentials'} + + filter_func = lambda obj: obj.host == content['host'] and obj.port == content['port'] # NOQA + extracted_server = list(filter(filter_func, objects))[0] + + assert content['host'] == extracted_server.host + assert content['port'] == extracted_server.port + assert content['credentials'] == extracted_server.credentials + + await GameServer.collection.delete_many({}) + + +@pytest.mark.asyncio +async def test_worker_returns_none_for_an_empty_list_of_servers(sanic_server): + await GameServer.collection.delete_many({}) + + client = RpcAmqpClient( + sanic_server.app, + routing_key=REQUEST_QUEUE, + request_exchange=REQUEST_EXCHANGE, + response_queue='', + response_exchange=RESPONSE_EXCHANGE + ) + response = await client.send(payload={ + 'required_slots': 10, + 'game_mode': 'team-deathmatch' + }) + + assert Response.EVENT_FIELD_NAME in response.keys() + assert Response.CONTENT_FIELD_NAME in response.keys() + content = response[Response.CONTENT_FIELD_NAME] + + assert content is None + + await GameServer.collection.delete_many({}) + + +@pytest.mark.asyncio +async def test_worker_returns_none_for_an_non_existing_server_type(sanic_server): + await GameServer.collection.delete_many({}) + + client = RpcAmqpClient( + sanic_server.app, + routing_key=REQUEST_QUEUE, + request_exchange=REQUEST_EXCHANGE, + response_queue='', + response_exchange=RESPONSE_EXCHANGE + ) + response = await client.send(payload={ + 'required_slots': 10, + 'game_mode': 'battle-royal' + }) + + assert Response.EVENT_FIELD_NAME in response.keys() + assert Response.CONTENT_FIELD_NAME in response.keys() + content = response[Response.CONTENT_FIELD_NAME] + + assert content is None + + await GameServer.collection.delete_many({}) + + +@pytest.mark.asyncio +async def test_worker_returns_a_validation_error_for_missing_fields(sanic_server): + await GameServer.collection.delete_many({}) + + client = RpcAmqpClient( + sanic_server.app, + routing_key=REQUEST_QUEUE, + request_exchange=REQUEST_EXCHANGE, + response_queue='', + response_exchange=RESPONSE_EXCHANGE + ) + response = await client.send(payload={}) + + assert Response.ERROR_FIELD_NAME in response.keys() + error = response[Response.ERROR_FIELD_NAME] + + assert Response.ERROR_TYPE_FIELD_NAME in error.keys() + assert error[Response.ERROR_TYPE_FIELD_NAME] == VALIDATION_ERROR + + assert Response.ERROR_DETAILS_FIELD_NAME in error.keys() + assert len(error[Response.ERROR_DETAILS_FIELD_NAME]) == 2 + + for field in ['required_slots', 'game_mode']: + assert field in error[Response.ERROR_DETAILS_FIELD_NAME] + assert len(error[Response.ERROR_DETAILS_FIELD_NAME][field]) == 1 + assert error[Response.ERROR_DETAILS_FIELD_NAME][field][0] == 'Missing data for ' \ + 'required field.' + + servers_count = await GameServer.collection.find().count() + assert servers_count == 0 + + await GameServer.collection.delete_many({}) From 1dd668402d09dcb965bca342b69fc156a6b60c53 Mon Sep 17 00:00:00 2001 From: Valeryi Savich Date: Sat, 26 May 2018 15:07:30 +0300 Subject: [PATCH 9/9] Implemented tests for RegisterServerWorker --- .../tests/test_register_server.py | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 game-servers-pool/tests/test_register_server.py diff --git a/game-servers-pool/tests/test_register_server.py b/game-servers-pool/tests/test_register_server.py new file mode 100644 index 0000000..cb3bf52 --- /dev/null +++ b/game-servers-pool/tests/test_register_server.py @@ -0,0 +1,207 @@ +from copy import deepcopy + +import pytest +from sage_utils.amqp.clients import RpcAmqpClient +from sage_utils.constants import VALIDATION_ERROR +from sage_utils.wrappers import Response + +from app.game_servers.documents import GameServer +from app.workers.register_server import RegisterServerWorker + + +REQUEST_QUEUE = RegisterServerWorker.QUEUE_NAME +REQUEST_EXCHANGE = RegisterServerWorker.REQUEST_EXCHANGE_NAME +RESPONSE_EXCHANGE = RegisterServerWorker.RESPONSE_EXCHANGE_NAME + + +@pytest.mark.asyncio +async def test_worker_returns_a_validation_error_for_missing_fields(sanic_server): + await GameServer.collection.delete_many({}) + + client = RpcAmqpClient( + sanic_server.app, + routing_key=REQUEST_QUEUE, + request_exchange=REQUEST_EXCHANGE, + response_queue='', + response_exchange=RESPONSE_EXCHANGE + ) + response = await client.send(payload={}) + + assert Response.ERROR_FIELD_NAME in response.keys() + error = response[Response.ERROR_FIELD_NAME] + + assert Response.ERROR_TYPE_FIELD_NAME in error.keys() + assert error[Response.ERROR_TYPE_FIELD_NAME] == VALIDATION_ERROR + + assert Response.ERROR_DETAILS_FIELD_NAME in error.keys() + assert len(error[Response.ERROR_DETAILS_FIELD_NAME]) == 4 + + for field in ['host', 'port', 'available_slots', 'game_mode']: + assert field in error[Response.ERROR_DETAILS_FIELD_NAME] + assert len(error[Response.ERROR_DETAILS_FIELD_NAME][field]) == 1 + assert error[Response.ERROR_DETAILS_FIELD_NAME][field][0] == 'Missing data for ' \ + 'required field.' + + servers_count = await GameServer.collection.find().count() + assert servers_count == 0 + + await GameServer.collection.delete_many({}) + + +@pytest.mark.asyncio +async def test_worker_registers_a_new_server_successfully(sanic_server): + await GameServer.collection.delete_many({}) + + client = RpcAmqpClient( + sanic_server.app, + routing_key=REQUEST_QUEUE, + request_exchange=REQUEST_EXCHANGE, + response_queue='', + response_exchange=RESPONSE_EXCHANGE + ) + response = await client.send(payload={ + 'host': '127.0.0.1', + 'port': 9000, + 'available_slots': 100, + 'credentials': { + 'token': 'super_secret_token' + }, + 'game_mode': '1v1' + }) + + assert Response.EVENT_FIELD_NAME in response.keys() + assert Response.CONTENT_FIELD_NAME in response.keys() + content = response[Response.CONTENT_FIELD_NAME] + + assert len(content.keys()) == 1 + assert 'id' in content.keys() + + servers_count = await GameServer.collection.find().count() + assert servers_count == 1 + + await GameServer.collection.delete_many({}) + + +@pytest.mark.asyncio +async def test_worker_updates_a_new_server_successfully(sanic_server): + await GameServer.collection.delete_many({}) + + create_data = { + 'host': '127.0.0.1', + 'port': 9000, + 'available_slots': 100, + 'credentials': { + 'token': 'super_secret_token' + }, + 'game_mode': '1v1' + } + + game_server = GameServer(**create_data) + await game_server.commit() + + update_data = deepcopy(create_data) + update_data.update({ + 'id': str(game_server.id), + 'available_slots': 80 + }) + + client = RpcAmqpClient( + sanic_server.app, + routing_key=REQUEST_QUEUE, + request_exchange=REQUEST_EXCHANGE, + response_queue='', + response_exchange=RESPONSE_EXCHANGE + ) + response = await client.send(payload=update_data) + + assert Response.EVENT_FIELD_NAME in response.keys() + assert Response.CONTENT_FIELD_NAME in response.keys() + content = response[Response.CONTENT_FIELD_NAME] + + assert len(content.keys()) == 1 + assert 'id' in content.keys() + + servers_count = await GameServer.collection.find().count() + assert servers_count == 1 + + await GameServer.collection.delete_many({}) + + +@pytest.mark.asyncio +async def test_worker_returns_a_validation_error_for_invalid_data(sanic_server): + await GameServer.collection.delete_many({}) + + client = RpcAmqpClient( + sanic_server.app, + routing_key=REQUEST_QUEUE, + request_exchange=REQUEST_EXCHANGE, + response_queue='', + response_exchange=RESPONSE_EXCHANGE + ) + response = await client.send(payload="/", raw_data=True) + + assert Response.ERROR_FIELD_NAME in response.keys() + error = response[Response.ERROR_FIELD_NAME] + + assert Response.ERROR_TYPE_FIELD_NAME in error.keys() + assert error[Response.ERROR_TYPE_FIELD_NAME] == VALIDATION_ERROR + + assert Response.ERROR_DETAILS_FIELD_NAME in error.keys() + assert len(error[Response.ERROR_DETAILS_FIELD_NAME]) == 4 + + for field in ['host', 'port', 'available_slots', 'game_mode']: + assert field in error[Response.ERROR_DETAILS_FIELD_NAME] + assert len(error[Response.ERROR_DETAILS_FIELD_NAME][field]) == 1 + assert error[Response.ERROR_DETAILS_FIELD_NAME][field][0] == 'Missing data for ' \ + 'required field.' + + servers_count = await GameServer.collection.find().count() + assert servers_count == 0 + + await GameServer.collection.delete_many({}) + + +@pytest.mark.asyncio +async def test_worker_returns_a_validation_error_for_invalid_id(sanic_server): + await GameServer.collection.delete_many({}) + + create_data = { + 'id': "INVALID_ID", + 'host': '127.0.0.1', + 'port': 9000, + 'available_slots': 100, + 'credentials': { + 'token': 'super_secret_token' + }, + 'game_mode': '1v1' + } + + client = RpcAmqpClient( + sanic_server.app, + routing_key=REQUEST_QUEUE, + request_exchange=REQUEST_EXCHANGE, + response_queue='', + response_exchange=RESPONSE_EXCHANGE + ) + response = await client.send(payload=create_data) + + assert Response.ERROR_FIELD_NAME in response.keys() + error = response[Response.ERROR_FIELD_NAME] + + assert Response.ERROR_TYPE_FIELD_NAME in error.keys() + assert error[Response.ERROR_TYPE_FIELD_NAME] == VALIDATION_ERROR + + assert Response.ERROR_DETAILS_FIELD_NAME in error.keys() + assert len(error[Response.ERROR_DETAILS_FIELD_NAME]) == 1 + + assert 'id' in error[Response.ERROR_DETAILS_FIELD_NAME] + assert len(error[Response.ERROR_DETAILS_FIELD_NAME]['id']) == 1 + assert error[Response.ERROR_DETAILS_FIELD_NAME]['id'][0] == "'INVALID_ID' is not a valid " \ + "ObjectId, it must be a 12-byte " \ + "input or a 24-character hex " \ + "string." + + servers_count = await GameServer.collection.find().count() + assert servers_count == 0 + + await GameServer.collection.delete_many({})