Compare commits

...

27 Commits
0.22 ... 0.30

Author SHA1 Message Date
Romuald Juchnowicz-Bierbasz
be6c0eb03e Increment version 2019-05-20 15:38:24 +02:00
Aliaksei Paulouski
0ee56193de Fix aiohttp exception hierarchy 2019-05-20 11:20:27 +02:00
Romuald Juchnowicz-Bierbasz
6bc91a12fa Register new requests 2019-05-17 16:42:13 +02:00
Romuald Juchnowicz-Bierbasz
6d513d86bf Increment version 2019-05-17 16:42:13 +02:00
Romuald Juchnowicz-Bierbasz
bdd2225262 Add new interface methods for game time and achievements 2019-05-17 16:42:12 +02:00
Rafal Makagon
68fdc4d188 Printing own port 2019-05-17 15:32:24 +02:00
Romuald Juchnowicz-Bierbasz
f283c10a95 Anonymise params in pass_login_credentials 2019-05-15 19:49:50 +02:00
Romuald Juchnowicz-Bierbasz
453734cefe Increment version 2019-05-10 17:21:31 +02:00
Romuald Juchnowicz-Bierbasz
85f1d83c28 Fix parameters 2019-05-10 17:21:05 +02:00
Romuald Juchnowicz-Bierbasz
701d3cf522 Increment version 2019-05-10 17:06:01 +02:00
Romuald Juchnowicz-Bierbasz
c8083b9006 Add cookie_jar param to HttpClient 2019-05-10 17:05:40 +02:00
Romuald Juchnowicz-Bierbasz
0608ade6d3 Fix name 2019-05-10 14:14:33 +02:00
Romuald Juchnowicz-Bierbasz
c349a3df8e Add timeout 2019-05-10 13:56:53 +02:00
Romuald Juchnowicz-Bierbasz
1fd959a665 Handle ServerDisconnectedError 2019-05-10 13:50:46 +02:00
Romuald Juchnowicz-Bierbasz
234a21d085 Add HttpClient 2019-05-10 13:36:32 +02:00
Romuald Juchnowicz-Bierbasz
90835ece58 Change project layout 2019-05-10 13:16:28 +02:00
Aliaksei Paulouski
9e1c8cfddd Add JS to NextStep params 2019-05-03 15:56:40 +02:00
Aliaksei Paulouski
f7f170b9ca Increment version 2019-05-03 15:56:40 +02:00
Romuald Juchnowicz-Bierbasz
8ad5ed76b7 Increment version 2019-04-30 16:59:13 +02:00
Romuald Juchnowicz-Bierbasz
7727098c6f SDK-2762: Fix error handling 2019-04-30 16:58:54 +02:00
Romuald Juchnowicz-Bierbasz
e53dc8f2c6 Merge branch 'master' into parameter-checking
* master:
  Add friends features
2019-04-30 14:50:23 +02:00
Romuald Juchnowicz-Bierbasz
527fd034bf Increment version 2019-04-29 15:45:18 +02:00
Romuald Juchnowicz-Bierbasz
6e251c6eb9 SDK-2762: Bind method params before calling 2019-04-29 15:45:03 +02:00
Romuald Juchnowicz-Bierbasz
dc9fc2cc5d SDK-2762: Standarize parameter binding 2019-04-29 14:51:12 +02:00
Aliaksei Paulouski
1fb79eb21a Add friends features 2019-04-26 11:08:49 +02:00
Romuald Juchnowicz-Bierbasz
7b9bcf86a1 Increment version 2019-04-16 14:53:57 +02:00
Romuald Juchnowicz-Bierbasz
30b3533e1d Old style namespace package 2019-04-16 14:53:28 +02:00
23 changed files with 525 additions and 206 deletions

View File

@@ -1,5 +0,0 @@
from unittest.mock import MagicMock
class AsyncMock(MagicMock):
async def __call__(self, *args, **kwargs):
return super(AsyncMock, self).__call__(*args, **kwargs)

View File

@@ -1,2 +1,8 @@
-e .
pytest==4.2.0 pytest==4.2.0
pytest-asyncio==0.10.0
pytest-mock==1.10.3
pytest-flakes==4.0.0 pytest-flakes==4.0.0
# because of pip bug https://github.com/pypa/pip/issues/4780
aiohttp==3.5.4
certifi==2019.3.9

View File

@@ -2,9 +2,14 @@ from setuptools import setup, find_packages
setup( setup(
name="galaxy.plugin.api", name="galaxy.plugin.api",
version="0.22", version="0.30",
description="Galaxy python plugin API", description="Galaxy python plugin API",
author='Galaxy team', author='Galaxy team',
author_email='galaxy@gog.com', author_email='galaxy@gog.com',
packages=find_packages(exclude=["tests"]) packages=find_packages("src"),
package_dir={'': 'src'},
install_requires=[
"aiohttp==3.5.4",
"certifi==2019.3.9"
]
) )

1
src/galaxy/__init__.py Normal file
View File

@@ -0,0 +1 @@
__path__ = __import__('pkgutil').extend_path(__path__, __name__)

View File

@@ -24,6 +24,7 @@ class Feature(Enum):
Chat = "Chat" Chat = "Chat"
ImportUsers = "ImportUsers" ImportUsers = "ImportUsers"
VerifyGame = "VerifyGame" VerifyGame = "VerifyGame"
ImportFriends = "ImportFriends"
class LicenseType(Enum): class LicenseType(Enum):
Unknown = "Unknown" Unknown = "Unknown"

View File

@@ -77,3 +77,7 @@ class IncoherentLastMessage(ApplicationError):
class MessageNotFound(ApplicationError): class MessageNotFound(ApplicationError):
def __init__(self, data=None): def __init__(self, data=None):
super().__init__(500, "Message not found", data) super().__init__(500, "Message not found", data)
class ImportInProgress(ApplicationError):
def __init__(self, data=None):
super().__init__(600, "Import already in progress", data)

View File

@@ -2,6 +2,7 @@ import asyncio
from collections import namedtuple from collections import namedtuple
from collections.abc import Iterable from collections.abc import Iterable
import logging import logging
import inspect
import json import json
class JsonRpcError(Exception): class JsonRpcError(Exception):
@@ -11,6 +12,9 @@ class JsonRpcError(Exception):
self.data = data self.data = data
super().__init__() super().__init__()
def __eq__(self, other):
return self.code == other.code and self.message == other.message and self.data == other.data
class ParseError(JsonRpcError): class ParseError(JsonRpcError):
def __init__(self): def __init__(self):
super().__init__(-32700, "Parse error") super().__init__(-32700, "Parse error")
@@ -46,7 +50,7 @@ class UnknownError(ApplicationError):
super().__init__(0, "Unknown error", data) super().__init__(0, "Unknown error", data)
Request = namedtuple("Request", ["method", "params", "id"], defaults=[{}, None]) Request = namedtuple("Request", ["method", "params", "id"], defaults=[{}, None])
Method = namedtuple("Method", ["callback", "internal", "sensitive_params"]) Method = namedtuple("Method", ["callback", "signature", "internal", "sensitive_params"])
def anonymise_sensitive_params(params, sensitive_params): def anonymise_sensitive_params(params, sensitive_params):
anomized_data = "****" anomized_data = "****"
@@ -81,7 +85,7 @@ class Server():
:param sensitive_params: list of parameters that will by anonymized before logging; if False - no params :param sensitive_params: list of parameters that will by anonymized before logging; if False - no params
are considered sensitive, if True - all params are considered sensitive are considered sensitive, if True - all params are considered sensitive
""" """
self._methods[name] = Method(callback, internal, sensitive_params) self._methods[name] = Method(callback, inspect.signature(callback), internal, sensitive_params)
def register_notification(self, name, callback, internal, sensitive_params=False): def register_notification(self, name, callback, internal, sensitive_params=False):
""" """
@@ -92,7 +96,7 @@ class Server():
:param sensitive_params: list of parameters that will by anonymized before logging; if False - no params :param sensitive_params: list of parameters that will by anonymized before logging; if False - no params
are considered sensitive, if True - all params are considered sensitive are considered sensitive, if True - all params are considered sensitive
""" """
self._notifications[name] = Method(callback, internal, sensitive_params) self._notifications[name] = Method(callback, inspect.signature(callback), internal, sensitive_params)
def register_eof(self, callback): def register_eof(self, callback):
self._eof_listeners.append(callback) self._eof_listeners.append(callback)
@@ -138,15 +142,20 @@ class Server():
logging.error("Received unknown notification: %s", request.method) logging.error("Received unknown notification: %s", request.method)
return return
callback, internal, sensitive_params = method callback, signature, internal, sensitive_params = method
self._log_request(request, sensitive_params) self._log_request(request, sensitive_params)
try:
bound_args = signature.bind(**request.params)
except TypeError:
self._send_error(request.id, InvalidParams())
if internal: if internal:
# internal requests are handled immediately # internal requests are handled immediately
callback(**request.params) callback(*bound_args.args, **bound_args.kwargs)
else: else:
try: try:
asyncio.create_task(callback(**request.params)) asyncio.create_task(callback(*bound_args.args, **bound_args.kwargs))
except Exception: except Exception:
logging.exception("Unexpected exception raised in notification handler") logging.exception("Unexpected exception raised in notification handler")
@@ -157,20 +166,23 @@ class Server():
self._send_error(request.id, MethodNotFound()) self._send_error(request.id, MethodNotFound())
return return
callback, internal, sensitive_params = method callback, signature, internal, sensitive_params = method
self._log_request(request, sensitive_params) self._log_request(request, sensitive_params)
try:
bound_args = signature.bind(**request.params)
except TypeError:
self._send_error(request.id, InvalidParams())
if internal: if internal:
# internal requests are handled immediately # internal requests are handled immediately
response = callback(request.params) response = callback(*bound_args.args, **bound_args.kwargs)
self._send_response(request.id, response) self._send_response(request.id, response)
else: else:
async def handle(): async def handle():
try: try:
result = await callback(request.params) result = await callback(*bound_args.args, **bound_args.kwargs)
self._send_response(request.id, result) self._send_response(request.id, result)
except TypeError:
self._send_error(request.id, InvalidParams())
except NotImplementedError: except NotImplementedError:
self._send_error(request.id, MethodNotFound()) self._send_error(request.id, MethodNotFound())
except JsonRpcError as error: except JsonRpcError as error:

View File

@@ -9,6 +9,7 @@ import sys
from galaxy.api.jsonrpc import Server, NotificationClient from galaxy.api.jsonrpc import Server, NotificationClient
from galaxy.api.consts import Feature from galaxy.api.consts import Feature
from galaxy.api.errors import UnknownError, ImportInProgress
class JSONEncoder(json.JSONEncoder): class JSONEncoder(json.JSONEncoder):
def default(self, o): # pylint: disable=method-hidden def default(self, o): # pylint: disable=method-hidden
@@ -41,14 +42,25 @@ class Plugin():
self._shutdown() self._shutdown()
self._server.register_eof(eof_handler) self._server.register_eof(eof_handler)
self._achievements_import_in_progress = False
self._game_times_import_in_progress = False
# internal # internal
self._register_method("shutdown", self._shutdown, internal=True) self._register_method("shutdown", self._shutdown, internal=True)
self._register_method("get_capabilities", self._get_capabilities, internal=True) self._register_method("get_capabilities", self._get_capabilities, internal=True)
self._register_method("ping", self._ping, internal=True) self._register_method("ping", self._ping, internal=True)
# implemented by developer # implemented by developer
self._register_method("init_authentication", self.authenticate, sensitive_params=["stored_credentials"]) self._register_method(
self._register_method("pass_login_credentials", self.pass_login_credentials) "init_authentication",
self.authenticate,
sensitive_params=["stored_credentials"]
)
self._register_method(
"pass_login_credentials",
self.pass_login_credentials,
sensitive_params=["cookies", "credentials"]
)
self._register_method( self._register_method(
"import_owned_games", "import_owned_games",
self.get_owned_games, self.get_owned_games,
@@ -61,6 +73,10 @@ class Plugin():
result_name="unlocked_achievements", result_name="unlocked_achievements",
feature=Feature.ImportAchievements feature=Feature.ImportAchievements
) )
self._register_method(
"start_achievements_import",
self.start_achievements_import,
)
self._register_method( self._register_method(
"import_local_games", "import_local_games",
self.get_local_games, self.get_local_games,
@@ -77,8 +93,8 @@ class Plugin():
self._register_method( self._register_method(
"import_friends", "import_friends",
self.get_friends, self.get_friends,
result_name="user_info_list", result_name="friend_info_list",
feature=Feature.ImportUsers feature=Feature.ImportFriends
) )
self._register_method( self._register_method(
"import_user_infos", "import_user_infos",
@@ -114,13 +130,16 @@ class Plugin():
result_name="messages", result_name="messages",
feature=Feature.Chat feature=Feature.Chat
) )
self._register_method( self._register_method(
"import_game_times", "import_game_times",
self.get_game_times, self.get_game_times,
result_name="game_times", result_name="game_times",
feature=Feature.ImportGameTime feature=Feature.ImportGameTime
) )
self._register_method(
"start_game_times_import",
self.start_game_times_import,
)
@property @property
def features(self): def features(self):
@@ -140,8 +159,8 @@ class Plugin():
def _register_method(self, name, handler, result_name=None, internal=False, sensitive_params=False, feature=None): def _register_method(self, name, handler, result_name=None, internal=False, sensitive_params=False, feature=None):
if internal: if internal:
def method(params): def method(*args, **kwargs):
result = handler(**params) result = handler(*args, **kwargs)
if result_name: if result_name:
result = { result = {
result_name: result result_name: result
@@ -149,8 +168,8 @@ class Plugin():
return result return result
self._server.register_method(name, method, True, sensitive_params) self._server.register_method(name, method, True, sensitive_params)
else: else:
async def method(params): async def method(*args, **kwargs):
result = await handler(**params) result = await handler(*args, **kwargs)
if result_name: if result_name:
result = { result = {
result_name: result result_name: result
@@ -222,22 +241,38 @@ class Plugin():
} }
self._notification_client.notify("achievement_unlocked", params) self._notification_client.notify("achievement_unlocked", params)
def game_achievements_import_success(self, game_id, achievements):
params = {
"game_id": game_id,
"unlocked_achievements": achievements
}
self._notification_client.notify("game_achievements_import_success", params)
def game_achievements_import_failure(self, game_id, error):
params = {
"game_id": game_id,
"error": {
"code": error.code,
"message": error.message
}
}
self._notification_client.notify("game_achievements_import_failure", params)
def achievements_import_finished(self):
self._notification_client.notify("achievements_import_finished", None)
def update_local_game_status(self, local_game): def update_local_game_status(self, local_game):
params = {"local_game" : local_game} params = {"local_game" : local_game}
self._notification_client.notify("local_game_status_changed", params) self._notification_client.notify("local_game_status_changed", params)
def add_friend(self, user): def add_friend(self, user):
params = {"user_info" : user} params = {"friend_info" : user}
self._notification_client.notify("friend_added", params) self._notification_client.notify("friend_added", params)
def remove_friend(self, user_id): def remove_friend(self, user_id):
params = {"user_id" : user_id} params = {"user_id" : user_id}
self._notification_client.notify("friend_removed", params) self._notification_client.notify("friend_removed", params)
def update_friend(self, user):
params = {"user_info" : user}
self._notification_client.notify("friend_updated", params)
def update_room(self, room_id, unread_message_count=None, new_messages=None): def update_room(self, room_id, unread_message_count=None, new_messages=None):
params = {"room_id": room_id} params = {"room_id": room_id}
if unread_message_count is not None: if unread_message_count is not None:
@@ -250,6 +285,23 @@ class Plugin():
params = {"game_time" : game_time} params = {"game_time" : game_time}
self._notification_client.notify("game_time_updated", params) self._notification_client.notify("game_time_updated", params)
def game_time_import_success(self, game_time):
params = {"game_time" : game_time}
self._notification_client.notify("game_time_import_success", params)
def game_time_import_failure(self, game_id, error):
params = {
"game_id": game_id,
"error": {
"code": error.code,
"message": error.message
}
}
self._notification_client.notify("game_time_import_failure", params)
def game_times_import_finished(self):
self._notification_client.notify("game_times_import_finished", None)
def lost_authentication(self): def lost_authentication(self):
self._notification_client.notify("authentication_lost", None) self._notification_client.notify("authentication_lost", None)
@@ -283,6 +335,28 @@ class Plugin():
async def get_unlocked_achievements(self, game_id): async def get_unlocked_achievements(self, game_id):
raise NotImplementedError() raise NotImplementedError()
async def start_achievements_import(self, game_ids):
if self._achievements_import_in_progress:
raise ImportInProgress()
async def import_games_achievements(game_ids):
async def import_game_achievements(game_id):
try:
achievements = await self.get_unlocked_achievements(game_id)
self.game_achievements_import_success(game_id, achievements)
except Exception as error:
self.game_achievements_import_failure(game_id, error)
try:
imports = [import_game_achievements(game_id) for game_id in game_ids]
await asyncio.gather(*imports)
finally:
self.achievements_import_finished()
self._achievements_import_in_progress = False
asyncio.create_task(import_games_achievements(game_ids))
self._achievements_import_in_progress = True
async def get_local_games(self): async def get_local_games(self):
raise NotImplementedError() raise NotImplementedError()
@@ -319,6 +393,32 @@ class Plugin():
async def get_game_times(self): async def get_game_times(self):
raise NotImplementedError() raise NotImplementedError()
async def start_game_times_import(self, game_ids):
if self._game_times_import_in_progress:
raise ImportInProgress()
async def import_game_times(game_ids):
try:
game_times = await self.get_game_times()
game_ids_set = set(game_ids)
for game_time in game_times:
if game_time.game_id not in game_ids_set:
continue
self.game_time_import_success(game_time)
game_ids_set.discard(game_time.game_id)
for game_id in game_ids_set:
self.game_time_import_failure(game_id, UnknownError())
except Exception as error:
for game_id in game_ids:
self.game_time_import_failure(game_id, error)
finally:
self.game_times_import_finished()
self._game_times_import_in_progress = False
asyncio.create_task(import_game_times(game_ids))
self._game_times_import_in_progress = True
def create_and_run_plugin(plugin_class, argv): def create_and_run_plugin(plugin_class, argv):
if len(argv) < 3: if len(argv) < 3:
logging.critical("Not enough parameters, required: token, port") logging.critical("Not enough parameters, required: token, port")
@@ -342,6 +442,8 @@ def create_and_run_plugin(plugin_class, argv):
async def coroutine(): async def coroutine():
reader, writer = await asyncio.open_connection("127.0.0.1", port) reader, writer = await asyncio.open_connection("127.0.0.1", port)
extra_info = writer.get_extra_info('sockname')
logging.info("Using local address: %s:%u", *extra_info)
plugin = plugin_class(reader, writer, token) plugin = plugin_class(reader, writer, token)
await plugin.run() await plugin.run()

View File

@@ -20,6 +20,7 @@ class NextStep():
next_step: str next_step: str
auth_params: Dict[str, str] auth_params: Dict[str, str]
cookies: Optional[List[Cookie]] = None cookies: Optional[List[Cookie]] = None
js: Optional[Dict[str, List[str]]] = None
@dataclass @dataclass
class LicenseInfo(): class LicenseInfo():
@@ -68,6 +69,11 @@ class UserInfo():
avatar_url: str avatar_url: str
presence: Presence presence: Presence
@dataclass
class FriendInfo():
user_id: str
user_name: str
@dataclass @dataclass
class Room(): class Room():
room_id: str room_id: str

47
src/galaxy/http.py Normal file
View File

@@ -0,0 +1,47 @@
import asyncio
import ssl
from http import HTTPStatus
import aiohttp
import certifi
from galaxy.api.errors import (
AccessDenied, AuthenticationRequired,
BackendTimeout, BackendNotAvailable, BackendError, NetworkError, UnknownBackendResponse, UnknownError
)
class HttpClient:
def __init__(self, limit=20, timeout=aiohttp.ClientTimeout(total=60), cookie_jar=None):
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context.load_verify_locations(certifi.where())
connector = aiohttp.TCPConnector(limit=limit, ssl=ssl_context)
self._session = aiohttp.ClientSession(connector=connector, timeout=timeout, cookie_jar=cookie_jar)
async def close(self):
await self._session.close()
async def request(self, method, *args, **kwargs):
try:
response = await self._session.request(method, *args, **kwargs)
except asyncio.TimeoutError:
raise BackendTimeout()
except aiohttp.ServerDisconnectedError:
raise BackendNotAvailable()
except aiohttp.ClientConnectionError:
raise NetworkError()
except aiohttp.ContentTypeError:
raise UnknownBackendResponse()
except aiohttp.ClientError:
raise UnknownError()
if response.status == HTTPStatus.UNAUTHORIZED:
raise AuthenticationRequired()
if response.status == HTTPStatus.FORBIDDEN:
raise AccessDenied()
if response.status == HTTPStatus.SERVICE_UNAVAILABLE:
raise BackendNotAvailable()
if response.status >= 500:
raise BackendError()
if response.status >= 400:
raise UnknownError()
return response

View File

View File

@@ -0,0 +1,12 @@
from asyncio import coroutine
from unittest.mock import MagicMock
class AsyncMock(MagicMock):
async def __call__(self, *args, **kwargs):
return super(AsyncMock, self).__call__(*args, **kwargs)
def coroutine_mock():
coro = MagicMock(name="CoroutineResult")
corofunc = MagicMock(name="CoroutineFunction", side_effect=coroutine(coro))
corofunc.coro = coro
return corofunc

View File

@@ -6,7 +6,7 @@ import pytest
from galaxy.api.plugin import Plugin from galaxy.api.plugin import Plugin
from galaxy.api.consts import Platform from galaxy.api.consts import Platform
from galaxy.unittest.mock import AsyncMock from galaxy.unittest.mock import AsyncMock, coroutine_mock
@pytest.fixture() @pytest.fixture()
def reader(): def reader():
@@ -57,7 +57,7 @@ def plugin(reader, writer):
with ExitStack() as stack: with ExitStack() as stack:
for method in async_methods: for method in async_methods:
stack.enter_context(patch.object(Plugin, method, new_callable=AsyncMock)) stack.enter_context(patch.object(Plugin, method, new_callable=coroutine_mock))
for method in methods: for method in methods:
stack.enter_context(patch.object(Plugin, method)) stack.enter_context(patch.object(Plugin, method))
yield Plugin(Platform.Generic, "0.1", reader, writer, "token") yield Plugin(Platform.Generic, "0.1", reader, writer, "token")

View File

@@ -1,9 +1,12 @@
import asyncio import asyncio
import json import json
from unittest.mock import call
import pytest
from pytest import raises from pytest import raises
from galaxy.api.types import Achievement from galaxy.api.types import Achievement
from galaxy.api.errors import UnknownError from galaxy.api.errors import UnknownError, ImportInProgress, BackendError
def test_initialization_no_unlock_time(): def test_initialization_no_unlock_time():
with raises(Exception): with raises(Exception):
@@ -23,7 +26,7 @@ def test_success(plugin, readline, write):
} }
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_unlocked_achievements.return_value = [ plugin.get_unlocked_achievements.coro.return_value = [
Achievement(achievement_id="lvl10", unlock_time=1548421241), Achievement(achievement_id="lvl10", unlock_time=1548421241),
Achievement(achievement_name="Got level 20", unlock_time=1548422395), Achievement(achievement_name="Got level 20", unlock_time=1548422395),
Achievement(achievement_id="lvl30", achievement_name="Got level 30", unlock_time=1548495633) Achievement(achievement_id="lvl30", achievement_name="Got level 30", unlock_time=1548495633)
@@ -65,7 +68,7 @@ def test_failure(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_unlocked_achievements.side_effect = UnknownError() plugin.get_unlocked_achievements.coro.side_effect = UnknownError()
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.get_unlocked_achievements.assert_called() plugin.get_unlocked_achievements.assert_called()
response = json.loads(write.call_args[0][0]) response = json.loads(write.call_args[0][0])
@@ -99,3 +102,92 @@ def test_unlock_achievement(plugin, write):
} }
} }
} }
@pytest.mark.asyncio
async def test_game_achievements_import_success(plugin, write):
achievements = [
Achievement(achievement_id="lvl10", unlock_time=1548421241),
Achievement(achievement_name="Got level 20", unlock_time=1548422395)
]
plugin.game_achievements_import_success("134", achievements)
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"method": "game_achievements_import_success",
"params": {
"game_id": "134",
"unlocked_achievements": [
{
"achievement_id": "lvl10",
"unlock_time": 1548421241
},
{
"achievement_name": "Got level 20",
"unlock_time": 1548422395
}
]
}
}
@pytest.mark.asyncio
async def test_game_achievements_import_failure(plugin, write):
plugin.game_achievements_import_failure("134", ImportInProgress())
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"method": "game_achievements_import_failure",
"params": {
"game_id": "134",
"error": {
"code": 600,
"message": "Import already in progress"
}
}
}
@pytest.mark.asyncio
async def test_achievements_import_finished(plugin, write):
plugin.achievements_import_finished()
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"method": "achievements_import_finished",
"params": None
}
@pytest.mark.asyncio
async def test_start_achievements_import(plugin, write, mocker):
game_achievements_import_success = mocker.patch.object(plugin, "game_achievements_import_success")
game_achievements_import_failure = mocker.patch.object(plugin, "game_achievements_import_failure")
achievements_import_finished = mocker.patch.object(plugin, "achievements_import_finished")
game_ids = ["1", "5", "9"]
error = BackendError()
achievements = [
Achievement(achievement_id="lvl10", unlock_time=1548421241),
Achievement(achievement_name="Got level 20", unlock_time=1548422395)
]
plugin.get_unlocked_achievements.coro.side_effect = [
achievements,
[],
error
]
await plugin.start_achievements_import(game_ids)
with pytest.raises(ImportInProgress):
await plugin.start_achievements_import(["4", "8"])
# wait until all tasks are finished
for _ in range(4):
await asyncio.sleep(0)
plugin.get_unlocked_achievements.coro.assert_has_calls([call("1"), call("5"), call("9")])
game_achievements_import_success.assert_has_calls([
call("1", achievements),
call("5", [])
])
game_achievements_import_failure.assert_called_once_with("9", error)
achievements_import_finished.assert_called_once_with()

View File

@@ -18,7 +18,7 @@ def test_success(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.authenticate.return_value = Authentication("132", "Zenek") plugin.authenticate.coro.return_value = Authentication("132", "Zenek")
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.authenticate.assert_called_with() plugin.authenticate.assert_called_with()
response = json.loads(write.call_args[0][0]) response = json.loads(write.call_args[0][0])
@@ -56,7 +56,7 @@ def test_failure(plugin, readline, write, error, code, message):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.authenticate.side_effect = error() plugin.authenticate.coro.side_effect = error()
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.authenticate.assert_called_with() plugin.authenticate.assert_called_with()
response = json.loads(write.call_args[0][0]) response = json.loads(write.call_args[0][0])
@@ -82,7 +82,7 @@ def test_stored_credentials(plugin, readline, write):
} }
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.authenticate.return_value = Authentication("132", "Zenek") plugin.authenticate.coro.return_value = Authentication("132", "Zenek")
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.authenticate.assert_called_with(stored_credentials={"token": "ABC"}) plugin.authenticate.assert_called_with(stored_credentials={"token": "ABC"})
write.assert_called() write.assert_called()

View File

@@ -21,7 +21,7 @@ def test_send_message_success(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.send_message.return_value = None plugin.send_message.coro.return_value = None
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.send_message.assert_called_with(room_id="14", message="Hello!") plugin.send_message.assert_called_with(room_id="14", message="Hello!")
response = json.loads(write.call_args[0][0]) response = json.loads(write.call_args[0][0])
@@ -52,7 +52,7 @@ def test_send_message_failure(plugin, readline, write, error, code, message):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.send_message.side_effect = error() plugin.send_message.coro.side_effect = error()
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.send_message.assert_called_with(room_id="15", message="Bye") plugin.send_message.assert_called_with(room_id="15", message="Bye")
response = json.loads(write.call_args[0][0]) response = json.loads(write.call_args[0][0])
@@ -78,7 +78,7 @@ def test_mark_as_read_success(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.mark_as_read.return_value = None plugin.mark_as_read.coro.return_value = None
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.mark_as_read.assert_called_with(room_id="14", last_message_id="67") plugin.mark_as_read.assert_called_with(room_id="14", last_message_id="67")
response = json.loads(write.call_args[0][0]) response = json.loads(write.call_args[0][0])
@@ -114,7 +114,7 @@ def test_mark_as_read_failure(plugin, readline, write, error, code, message):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.mark_as_read.side_effect = error() plugin.mark_as_read.coro.side_effect = error()
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.mark_as_read.assert_called_with(room_id="18", last_message_id="7") plugin.mark_as_read.assert_called_with(room_id="18", last_message_id="7")
response = json.loads(write.call_args[0][0]) response = json.loads(write.call_args[0][0])
@@ -136,7 +136,7 @@ def test_get_rooms_success(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_rooms.return_value = [ plugin.get_rooms.coro.return_value = [
Room("13", 0, None), Room("13", 0, None),
Room("15", 34, "8") Room("15", 34, "8")
] ]
@@ -170,7 +170,7 @@ def test_get_rooms_failure(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_rooms.side_effect = UnknownError() plugin.get_rooms.coro.side_effect = UnknownError()
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.get_rooms.assert_called_with() plugin.get_rooms.assert_called_with()
response = json.loads(write.call_args[0][0]) response = json.loads(write.call_args[0][0])
@@ -196,7 +196,7 @@ def test_get_room_history_from_message_success(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_room_history_from_message.return_value = [ plugin.get_room_history_from_message.coro.return_value = [
Message("13", "149", 1549454837, "Hello"), Message("13", "149", 1549454837, "Hello"),
Message("14", "812", 1549454899, "Hi") Message("14", "812", 1549454899, "Hi")
] ]
@@ -245,7 +245,7 @@ def test_get_room_history_from_message_failure(plugin, readline, write, error, c
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_room_history_from_message.side_effect = error() plugin.get_room_history_from_message.coro.side_effect = error()
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.get_room_history_from_message.assert_called_with(room_id="33", message_id="88") plugin.get_room_history_from_message.assert_called_with(room_id="33", message_id="88")
response = json.loads(write.call_args[0][0]) response = json.loads(write.call_args[0][0])
@@ -271,7 +271,7 @@ def test_get_room_history_from_timestamp_success(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_room_history_from_timestamp.return_value = [ plugin.get_room_history_from_timestamp.coro.return_value = [
Message("12", "155", 1549454836, "Bye") Message("12", "155", 1549454836, "Bye")
] ]
asyncio.run(plugin.run()) asyncio.run(plugin.run())
@@ -308,7 +308,7 @@ def test_get_room_history_from_timestamp_failure(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_room_history_from_timestamp.side_effect = UnknownError() plugin.get_room_history_from_timestamp.coro.side_effect = UnknownError()
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.get_room_history_from_timestamp.assert_called_with( plugin.get_room_history_from_timestamp.assert_called_with(
room_id="10", room_id="10",

90
tests/test_friends.py Normal file
View File

@@ -0,0 +1,90 @@
import asyncio
import json
from galaxy.api.types import FriendInfo
from galaxy.api.errors import UnknownError
def test_get_friends_success(plugin, readline, write):
request = {
"jsonrpc": "2.0",
"id": "3",
"method": "import_friends"
}
readline.side_effect = [json.dumps(request), ""]
plugin.get_friends.coro.return_value = [
FriendInfo("3", "Jan"),
FriendInfo("5", "Ola")
]
asyncio.run(plugin.run())
plugin.get_friends.assert_called_with()
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"id": "3",
"result": {
"friend_info_list": [
{"user_id": "3", "user_name": "Jan"},
{"user_id": "5", "user_name": "Ola"}
]
}
}
def test_get_friends_failure(plugin, readline, write):
request = {
"jsonrpc": "2.0",
"id": "3",
"method": "import_friends"
}
readline.side_effect = [json.dumps(request), ""]
plugin.get_friends.coro.side_effect = UnknownError()
asyncio.run(plugin.run())
plugin.get_friends.assert_called_with()
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"id": "3",
"error": {
"code": 0,
"message": "Unknown error",
}
}
def test_add_friend(plugin, write):
friend = FriendInfo("7", "Kuba")
async def couritine():
plugin.add_friend(friend)
asyncio.run(couritine())
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"method": "friend_added",
"params": {
"friend_info": {"user_id": "7", "user_name": "Kuba"}
}
}
def test_remove_friend(plugin, write):
async def couritine():
plugin.remove_friend("5")
asyncio.run(couritine())
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"method": "friend_removed",
"params": {
"user_id": "5"
}
}

View File

@@ -1,8 +1,10 @@
import asyncio import asyncio
import json import json
from unittest.mock import call
import pytest
from galaxy.api.types import GameTime from galaxy.api.types import GameTime
from galaxy.api.errors import UnknownError from galaxy.api.errors import UnknownError, ImportInProgress, BackendError
def test_success(plugin, readline, write): def test_success(plugin, readline, write):
request = { request = {
@@ -12,7 +14,7 @@ def test_success(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_game_times.return_value = [ plugin.get_game_times.coro.return_value = [
GameTime("3", 60, 1549550504), GameTime("3", 60, 1549550504),
GameTime("5", 10, 1549550502) GameTime("5", 10, 1549550502)
] ]
@@ -47,7 +49,7 @@ def test_failure(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_game_times.side_effect = UnknownError() plugin.get_game_times.coro.side_effect = UnknownError()
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.get_game_times.assert_called_with() plugin.get_game_times.assert_called_with()
response = json.loads(write.call_args[0][0]) response = json.loads(write.call_args[0][0])
@@ -81,3 +83,93 @@ def test_update_game(plugin, write):
} }
} }
} }
@pytest.mark.asyncio
async def test_game_time_import_success(plugin, write):
plugin.game_time_import_success(GameTime("3", 60, 1549550504))
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"method": "game_time_import_success",
"params": {
"game_time": {
"game_id": "3",
"time_played": 60,
"last_played_time": 1549550504
}
}
}
@pytest.mark.asyncio
async def test_game_time_import_failure(plugin, write):
plugin.game_time_import_failure("134", ImportInProgress())
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"method": "game_time_import_failure",
"params": {
"game_id": "134",
"error": {
"code": 600,
"message": "Import already in progress"
}
}
}
@pytest.mark.asyncio
async def test_game_times_import_finished(plugin, write):
plugin.game_times_import_finished()
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"method": "game_times_import_finished",
"params": None
}
@pytest.mark.asyncio
async def test_start_game_times_import(plugin, write, mocker):
game_time_import_success = mocker.patch.object(plugin, "game_time_import_success")
game_time_import_failure = mocker.patch.object(plugin, "game_time_import_failure")
game_times_import_finished = mocker.patch.object(plugin, "game_times_import_finished")
game_ids = ["1", "5"]
game_time = GameTime("1", 10, 1549550502)
plugin.get_game_times.coro.return_value = [
game_time
]
await plugin.start_game_times_import(game_ids)
with pytest.raises(ImportInProgress):
await plugin.start_game_times_import(["4", "8"])
# wait until all tasks are finished
for _ in range(4):
await asyncio.sleep(0)
plugin.get_game_times.coro.assert_called_once_with()
game_time_import_success.assert_called_once_with(game_time)
game_time_import_failure.assert_called_once_with("5", UnknownError())
game_times_import_finished.assert_called_once_with()
@pytest.mark.asyncio
async def test_start_game_times_import_failure(plugin, write, mocker):
game_time_import_failure = mocker.patch.object(plugin, "game_time_import_failure")
game_times_import_finished = mocker.patch.object(plugin, "game_times_import_finished")
game_ids = ["1", "5"]
error = BackendError()
plugin.get_game_times.coro.side_effect = error
await plugin.start_game_times_import(game_ids)
# wait until all tasks are finished
for _ in range(4):
await asyncio.sleep(0)
plugin.get_game_times.coro.assert_called_once_with()
assert game_time_import_failure.mock_calls == [call("1", error), call("5", error)]
game_times_import_finished.assert_called_once_with()

View File

@@ -16,7 +16,7 @@ def test_success(plugin, readline, write):
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_local_games.return_value = [ plugin.get_local_games.coro.return_value = [
LocalGame("1", LocalGameState.Running), LocalGame("1", LocalGameState.Running),
LocalGame("2", LocalGameState.Installed), LocalGame("2", LocalGameState.Installed),
LocalGame("3", LocalGameState.Installed | LocalGameState.Running) LocalGame("3", LocalGameState.Installed | LocalGameState.Running)
@@ -61,7 +61,7 @@ def test_failure(plugin, readline, write, error, code, message):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_local_games.side_effect = error() plugin.get_local_games.coro.side_effect = error()
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.get_local_games.assert_called_with() plugin.get_local_games.assert_called_with()
response = json.loads(write.call_args[0][0]) response = json.loads(write.call_args[0][0])

View File

@@ -13,7 +13,7 @@ def test_success(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_owned_games.return_value = [ plugin.get_owned_games.coro.return_value = [
Game("3", "Doom", None, LicenseInfo(LicenseType.SinglePurchase, None)), Game("3", "Doom", None, LicenseInfo(LicenseType.SinglePurchase, None)),
Game( Game(
"5", "5",
@@ -75,7 +75,7 @@ def test_failure(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_owned_games.side_effect = UnknownError() plugin.get_owned_games.coro.side_effect = UnknownError()
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.get_owned_games.assert_called_with() plugin.get_owned_games.assert_called_with()
response = json.loads(write.call_args[0][0]) response = json.loads(write.call_args[0][0])

View File

@@ -5,153 +5,6 @@ from galaxy.api.types import UserInfo, Presence
from galaxy.api.errors import UnknownError from galaxy.api.errors import UnknownError
from galaxy.api.consts import PresenceState from galaxy.api.consts import PresenceState
def test_get_friends_success(plugin, readline, write):
request = {
"jsonrpc": "2.0",
"id": "3",
"method": "import_friends"
}
readline.side_effect = [json.dumps(request), ""]
plugin.get_friends.return_value = [
UserInfo(
"3",
True,
"Jan",
"http://avatar1.png",
Presence(
PresenceState.Online,
"123",
"Main menu"
)
),
UserInfo(
"5",
True,
"Ola",
"http://avatar2.png",
Presence(PresenceState.Offline)
)
]
asyncio.run(plugin.run())
plugin.get_friends.assert_called_with()
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"id": "3",
"result": {
"user_info_list": [
{
"user_id": "3",
"is_friend": True,
"user_name": "Jan",
"avatar_url": "http://avatar1.png",
"presence": {
"presence_state": "online",
"game_id": "123",
"presence_status": "Main menu"
}
},
{
"user_id": "5",
"is_friend": True,
"user_name": "Ola",
"avatar_url": "http://avatar2.png",
"presence": {
"presence_state": "offline"
}
}
]
}
}
def test_get_friends_failure(plugin, readline, write):
request = {
"jsonrpc": "2.0",
"id": "3",
"method": "import_friends"
}
readline.side_effect = [json.dumps(request), ""]
plugin.get_friends.side_effect = UnknownError()
asyncio.run(plugin.run())
plugin.get_friends.assert_called_with()
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"id": "3",
"error": {
"code": 0,
"message": "Unknown error",
}
}
def test_add_friend(plugin, write):
friend = UserInfo("7", True, "Kuba", "http://avatar.png", Presence(PresenceState.Offline))
async def couritine():
plugin.add_friend(friend)
asyncio.run(couritine())
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"method": "friend_added",
"params": {
"user_info": {
"user_id": "7",
"is_friend": True,
"user_name": "Kuba",
"avatar_url": "http://avatar.png",
"presence": {
"presence_state": "offline"
}
}
}
}
def test_remove_friend(plugin, write):
async def couritine():
plugin.remove_friend("5")
asyncio.run(couritine())
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"method": "friend_removed",
"params": {
"user_id": "5"
}
}
def test_update_friend(plugin, write):
friend = UserInfo("9", True, "Anna", "http://avatar.png", Presence(PresenceState.Offline))
async def couritine():
plugin.update_friend(friend)
asyncio.run(couritine())
response = json.loads(write.call_args[0][0])
assert response == {
"jsonrpc": "2.0",
"method": "friend_updated",
"params": {
"user_info": {
"user_id": "9",
"is_friend": True,
"user_name": "Anna",
"avatar_url": "http://avatar.png",
"presence": {
"presence_state": "offline"
}
}
}
}
def test_get_users_success(plugin, readline, write): def test_get_users_success(plugin, readline, write):
request = { request = {
@@ -164,7 +17,7 @@ def test_get_users_success(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_users.return_value = [ plugin.get_users.coro.return_value = [
UserInfo("5", False, "Ula", "http://avatar.png", Presence(PresenceState.Offline)) UserInfo("5", False, "Ula", "http://avatar.png", Presence(PresenceState.Offline))
] ]
asyncio.run(plugin.run()) asyncio.run(plugin.run())
@@ -189,6 +42,7 @@ def test_get_users_success(plugin, readline, write):
} }
} }
def test_get_users_failure(plugin, readline, write): def test_get_users_failure(plugin, readline, write):
request = { request = {
"jsonrpc": "2.0", "jsonrpc": "2.0",
@@ -200,7 +54,7 @@ def test_get_users_failure(plugin, readline, write):
} }
readline.side_effect = [json.dumps(request), ""] readline.side_effect = [json.dumps(request), ""]
plugin.get_users.side_effect = UnknownError() plugin.get_users.coro.side_effect = UnknownError()
asyncio.run(plugin.run()) asyncio.run(plugin.run())
plugin.get_users.assert_called_with(user_id_list=["10", "11", "12"]) plugin.get_users.assert_called_with(user_id_list=["10", "11", "12"])
response = json.loads(write.call_args[0][0]) response = json.loads(write.call_args[0][0])