mirror of
https://github.com/gogcom/galaxy-integrations-python-api.git
synced 2026-01-01 11:28:12 -05:00
Compare commits
53 Commits
0.21
...
deployed_0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed91fd582c | ||
|
|
bd393a96f0 | ||
|
|
c53aab1abb | ||
|
|
80f40b1971 | ||
|
|
0da0296154 | ||
|
|
9a115557b3 | ||
|
|
14c2d7d9e8 | ||
|
|
4a7a759cea | ||
|
|
da8da24b01 | ||
|
|
ccbb13e685 | ||
|
|
a3ca815975 | ||
|
|
f2d4127a31 | ||
|
|
07b6edce12 | ||
|
|
ef7f9ccca1 | ||
|
|
3b296cbcc9 | ||
|
|
f5361cd5ab | ||
|
|
758909efba | ||
|
|
0bc8000f14 | ||
|
|
e62e7e0e6e | ||
|
|
be6c0eb03e | ||
|
|
0ee56193de | ||
|
|
6bc91a12fa | ||
|
|
6d513d86bf | ||
|
|
bdd2225262 | ||
|
|
68fdc4d188 | ||
|
|
f283c10a95 | ||
|
|
453734cefe | ||
|
|
85f1d83c28 | ||
|
|
701d3cf522 | ||
|
|
c8083b9006 | ||
|
|
0608ade6d3 | ||
|
|
c349a3df8e | ||
|
|
1fd959a665 | ||
|
|
234a21d085 | ||
|
|
90835ece58 | ||
|
|
9e1c8cfddd | ||
|
|
f7f170b9ca | ||
|
|
8ad5ed76b7 | ||
|
|
7727098c6f | ||
|
|
e53dc8f2c6 | ||
|
|
527fd034bf | ||
|
|
6e251c6eb9 | ||
|
|
dc9fc2cc5d | ||
|
|
1fb79eb21a | ||
|
|
7b9bcf86a1 | ||
|
|
30b3533e1d | ||
|
|
92b1d8e4df | ||
|
|
4adef2dace | ||
|
|
1430fe39d7 | ||
|
|
c591efc493 | ||
|
|
7c4f3fba5b | ||
|
|
f2e2e41d04 | ||
|
|
25b850d8bb |
56
README.md
56
README.md
@@ -1,24 +1,52 @@
|
||||
# Galaxy python plugin API
|
||||
# GOG Galaxy - Community Integration - Python API
|
||||
|
||||
## Usage
|
||||
This document is still work in progress.
|
||||
|
||||
Implement plugin:
|
||||
## Basic Usage
|
||||
|
||||
Basic implementation:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from galaxy.api.plugin import Plugin
|
||||
import sys
|
||||
from galaxy.api.plugin import Plugin, create_and_run_plugin
|
||||
from galaxy.api.consts import Platform
|
||||
|
||||
class PluginExample(Plugin):
|
||||
def __init__(self, reader, writer, token):
|
||||
super().__init__(
|
||||
Platform.Generic, # Choose platform from available list
|
||||
"0.1", # Version
|
||||
reader,
|
||||
writer,
|
||||
token
|
||||
)
|
||||
|
||||
# implement methods
|
||||
async def authenticate(self, stored_credentials=None):
|
||||
pass
|
||||
|
||||
def main():
|
||||
create_and_run_plugin(PluginExample, sys.argv)
|
||||
|
||||
# run plugin event loop
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(MockPlugin().run())
|
||||
main()
|
||||
```
|
||||
|
||||
Use [pyinstaller](https://www.pyinstaller.org/) to create plugin executbale.
|
||||
Plugin should be deployed with manifest:
|
||||
```json
|
||||
{
|
||||
"name": "Example plugin",
|
||||
"platform": "generic",
|
||||
"guid": "UNIQUE-GUID",
|
||||
"version": "0.1",
|
||||
"description": "Example plugin",
|
||||
"author": "Name",
|
||||
"email": "author@email.com",
|
||||
"url": "https://github.com/user/galaxy-plugin-example",
|
||||
"script": "plugin.py"
|
||||
}
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
@@ -31,15 +59,9 @@ Run tests:
|
||||
```bash
|
||||
pytest
|
||||
```
|
||||
## Methods Documentation
|
||||
TODO
|
||||
|
||||
## Changelog
|
||||
## Legal Notice
|
||||
|
||||
### 0.21
|
||||
* Add `Epic` platform.
|
||||
### 0.16
|
||||
* Do not log sensitive data.
|
||||
* Return `LocalGameState` as int (possible combination of flags).
|
||||
### 0.15
|
||||
* `shutdown()` is called on socket disconnection.
|
||||
### 0.14
|
||||
* Added required version parameter to Plugin constructor.
|
||||
By integrating or attempting to integrate any applications or content with or into GOG Galaxy® 2.0. you represent that such application or content is your original creation (other than any software made available by GOG) and/or that you have all necessary rights to grant such applicable rights to the relevant community integration to GOG and to GOG Galaxy 2.0 end users for the purpose of use of such community integration and that such community integration comply with any third party license and other requirements including compliance with applicable laws.
|
||||
|
||||
14
jenkins/release.groovy
Normal file
14
jenkins/release.groovy
Normal file
@@ -0,0 +1,14 @@
|
||||
stage('Upload to github')
|
||||
{
|
||||
node('ActiveClientMacosxBuilder') {
|
||||
deleteDir()
|
||||
checkout scm
|
||||
withPythonEnv('/usr/local/bin/python3.7') {
|
||||
withCredentials([string(credentialsId: 'github_goggalaxy', variable: 'GITHUB_TOKEN')]) {
|
||||
sh 'pip install -r jenkins/requirements.txt'
|
||||
def version = sh(returnStdout: true, script: 'python setup.py --version').trim()
|
||||
sh "python jenkins/release.py $version"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
jenkins/release.py
Normal file
26
jenkins/release.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import os
|
||||
import sys
|
||||
from galaxy.github.exporter import transfer_repo
|
||||
|
||||
GITHUB_USERNAME = "goggalaxy"
|
||||
GITHUB_EMAIL = "galaxy-sdk@gog.com"
|
||||
GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]
|
||||
GITHUB_REPO_NAME = "galaxy-integrations-python-api"
|
||||
SOURCE_BRANCH = os.environ["GIT_REFSPEC"]
|
||||
|
||||
GITLAB_USERNAME = "galaxy-client"
|
||||
GITLAB_REPO_NAME = "galaxy-plugin-api"
|
||||
|
||||
def version_provider(_):
|
||||
return sys.argv[1]
|
||||
|
||||
gh_version = transfer_repo(
|
||||
version_provider=version_provider,
|
||||
source_repo_spec="git@gitlab.gog.com:{}/{}.git".format(GITLAB_USERNAME, GITLAB_REPO_NAME),
|
||||
source_include_elements=["src", "tests", "requirements.txt", ".gitignore", "*.md", "pytest.ini", "setup.py"],
|
||||
source_branch=SOURCE_BRANCH,
|
||||
dest_repo_spec="https://{}:{}@github.com/{}/{}.git".format(GITHUB_USERNAME, GITHUB_TOKEN, "gogcom", GITHUB_REPO_NAME),
|
||||
dest_branch="master",
|
||||
dest_user_email=GITHUB_EMAIL,
|
||||
dest_user_name="GOG Galaxy SDK Team"
|
||||
)
|
||||
1
jenkins/requirements.txt
Normal file
1
jenkins/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
git+ssh://git@gitlab.gog.com/galaxy-client/github-exporter.git@v0.1
|
||||
@@ -1,2 +1,8 @@
|
||||
-e .
|
||||
pytest==4.2.0
|
||||
pytest-asyncio==0.10.0
|
||||
pytest-mock==1.10.3
|
||||
pytest-flakes==4.0.0
|
||||
# because of pip bug https://github.com/pypa/pip/issues/4780
|
||||
aiohttp==3.5.4
|
||||
certifi==2019.3.9
|
||||
|
||||
9
setup.py
9
setup.py
@@ -2,9 +2,14 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="galaxy.plugin.api",
|
||||
version="0.21",
|
||||
version="0.31.3",
|
||||
description="Galaxy python plugin API",
|
||||
author='Galaxy team',
|
||||
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
1
src/galaxy/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
||||
@@ -24,6 +24,7 @@ class Feature(Enum):
|
||||
Chat = "Chat"
|
||||
ImportUsers = "ImportUsers"
|
||||
VerifyGame = "VerifyGame"
|
||||
ImportFriends = "ImportFriends"
|
||||
|
||||
class LicenseType(Enum):
|
||||
Unknown = "Unknown"
|
||||
@@ -77,3 +77,7 @@ class IncoherentLastMessage(ApplicationError):
|
||||
class MessageNotFound(ApplicationError):
|
||||
def __init__(self, data=None):
|
||||
super().__init__(500, "Message not found", data)
|
||||
|
||||
class ImportInProgress(ApplicationError):
|
||||
def __init__(self, data=None):
|
||||
super().__init__(600, "Import already in progress", data)
|
||||
@@ -2,6 +2,7 @@ import asyncio
|
||||
from collections import namedtuple
|
||||
from collections.abc import Iterable
|
||||
import logging
|
||||
import inspect
|
||||
import json
|
||||
|
||||
class JsonRpcError(Exception):
|
||||
@@ -11,6 +12,9 @@ class JsonRpcError(Exception):
|
||||
self.data = data
|
||||
super().__init__()
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.code == other.code and self.message == other.message and self.data == other.data
|
||||
|
||||
class ParseError(JsonRpcError):
|
||||
def __init__(self):
|
||||
super().__init__(-32700, "Parse error")
|
||||
@@ -46,7 +50,7 @@ class UnknownError(ApplicationError):
|
||||
super().__init__(0, "Unknown error", data)
|
||||
|
||||
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):
|
||||
anomized_data = "****"
|
||||
@@ -81,7 +85,7 @@ class Server():
|
||||
: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
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
@@ -92,7 +96,7 @@ class Server():
|
||||
: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
|
||||
"""
|
||||
self._notifications[name] = Method(callback, internal, sensitive_params)
|
||||
self._notifications[name] = Method(callback, inspect.signature(callback), internal, sensitive_params)
|
||||
|
||||
def register_eof(self, callback):
|
||||
self._eof_listeners.append(callback)
|
||||
@@ -138,15 +142,20 @@ class Server():
|
||||
logging.error("Received unknown notification: %s", request.method)
|
||||
return
|
||||
|
||||
callback, internal, sensitive_params = method
|
||||
callback, signature, internal, sensitive_params = method
|
||||
self._log_request(request, sensitive_params)
|
||||
|
||||
try:
|
||||
bound_args = signature.bind(**request.params)
|
||||
except TypeError:
|
||||
self._send_error(request.id, InvalidParams())
|
||||
|
||||
if internal:
|
||||
# internal requests are handled immediately
|
||||
callback(**request.params)
|
||||
callback(*bound_args.args, **bound_args.kwargs)
|
||||
else:
|
||||
try:
|
||||
asyncio.create_task(callback(**request.params))
|
||||
asyncio.create_task(callback(*bound_args.args, **bound_args.kwargs))
|
||||
except Exception:
|
||||
logging.exception("Unexpected exception raised in notification handler")
|
||||
|
||||
@@ -157,20 +166,23 @@ class Server():
|
||||
self._send_error(request.id, MethodNotFound())
|
||||
return
|
||||
|
||||
callback, internal, sensitive_params = method
|
||||
callback, signature, internal, sensitive_params = method
|
||||
self._log_request(request, sensitive_params)
|
||||
|
||||
try:
|
||||
bound_args = signature.bind(**request.params)
|
||||
except TypeError:
|
||||
self._send_error(request.id, InvalidParams())
|
||||
|
||||
if internal:
|
||||
# internal requests are handled immediately
|
||||
response = callback(request.params)
|
||||
response = callback(*bound_args.args, **bound_args.kwargs)
|
||||
self._send_response(request.id, response)
|
||||
else:
|
||||
async def handle():
|
||||
try:
|
||||
result = await callback(request.params)
|
||||
result = await callback(*bound_args.args, **bound_args.kwargs)
|
||||
self._send_response(request.id, result)
|
||||
except TypeError:
|
||||
self._send_error(request.id, InvalidParams())
|
||||
except NotImplementedError:
|
||||
self._send_error(request.id, MethodNotFound())
|
||||
except JsonRpcError as error:
|
||||
@@ -9,6 +9,7 @@ import sys
|
||||
|
||||
from galaxy.api.jsonrpc import Server, NotificationClient
|
||||
from galaxy.api.consts import Feature
|
||||
from galaxy.api.errors import UnknownError, ImportInProgress
|
||||
|
||||
class JSONEncoder(json.JSONEncoder):
|
||||
def default(self, o): # pylint: disable=method-hidden
|
||||
@@ -41,14 +42,25 @@ class Plugin():
|
||||
self._shutdown()
|
||||
self._server.register_eof(eof_handler)
|
||||
|
||||
self._achievements_import_in_progress = False
|
||||
self._game_times_import_in_progress = False
|
||||
|
||||
# internal
|
||||
self._register_method("shutdown", self._shutdown, internal=True)
|
||||
self._register_method("get_capabilities", self._get_capabilities, internal=True)
|
||||
self._register_method("ping", self._ping, internal=True)
|
||||
|
||||
# implemented by developer
|
||||
self._register_method("init_authentication", self.authenticate, sensitive_params=["stored_credentials"])
|
||||
self._register_method("pass_login_credentials", self.pass_login_credentials)
|
||||
self._register_method(
|
||||
"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(
|
||||
"import_owned_games",
|
||||
self.get_owned_games,
|
||||
@@ -61,6 +73,10 @@ class Plugin():
|
||||
result_name="unlocked_achievements",
|
||||
feature=Feature.ImportAchievements
|
||||
)
|
||||
self._register_method(
|
||||
"start_achievements_import",
|
||||
self.start_achievements_import,
|
||||
)
|
||||
self._register_method(
|
||||
"import_local_games",
|
||||
self.get_local_games,
|
||||
@@ -77,8 +93,8 @@ class Plugin():
|
||||
self._register_method(
|
||||
"import_friends",
|
||||
self.get_friends,
|
||||
result_name="user_info_list",
|
||||
feature=Feature.ImportUsers
|
||||
result_name="friend_info_list",
|
||||
feature=Feature.ImportFriends
|
||||
)
|
||||
self._register_method(
|
||||
"import_user_infos",
|
||||
@@ -114,13 +130,16 @@ class Plugin():
|
||||
result_name="messages",
|
||||
feature=Feature.Chat
|
||||
)
|
||||
|
||||
self._register_method(
|
||||
"import_game_times",
|
||||
self.get_game_times,
|
||||
result_name="game_times",
|
||||
feature=Feature.ImportGameTime
|
||||
)
|
||||
self._register_method(
|
||||
"start_game_times_import",
|
||||
self.start_game_times_import,
|
||||
)
|
||||
|
||||
@property
|
||||
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):
|
||||
if internal:
|
||||
def method(params):
|
||||
result = handler(**params)
|
||||
def method(*args, **kwargs):
|
||||
result = handler(*args, **kwargs)
|
||||
if result_name:
|
||||
result = {
|
||||
result_name: result
|
||||
@@ -149,8 +168,8 @@ class Plugin():
|
||||
return result
|
||||
self._server.register_method(name, method, True, sensitive_params)
|
||||
else:
|
||||
async def method(params):
|
||||
result = await handler(**params)
|
||||
async def method(*args, **kwargs):
|
||||
result = await handler(*args, **kwargs)
|
||||
if result_name:
|
||||
result = {
|
||||
result_name: result
|
||||
@@ -222,22 +241,38 @@ class Plugin():
|
||||
}
|
||||
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):
|
||||
params = {"local_game" : local_game}
|
||||
self._notification_client.notify("local_game_status_changed", params)
|
||||
|
||||
def add_friend(self, user):
|
||||
params = {"user_info" : user}
|
||||
params = {"friend_info" : user}
|
||||
self._notification_client.notify("friend_added", params)
|
||||
|
||||
def remove_friend(self, user_id):
|
||||
params = {"user_id" : user_id}
|
||||
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):
|
||||
params = {"room_id": room_id}
|
||||
if unread_message_count is not None:
|
||||
@@ -250,6 +285,23 @@ class Plugin():
|
||||
params = {"game_time" : game_time}
|
||||
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):
|
||||
self._notification_client.notify("authentication_lost", None)
|
||||
|
||||
@@ -283,6 +335,32 @@ class Plugin():
|
||||
async def get_unlocked_achievements(self, game_id):
|
||||
raise NotImplementedError()
|
||||
|
||||
async def start_achievements_import(self, game_ids):
|
||||
if self._achievements_import_in_progress:
|
||||
raise ImportInProgress()
|
||||
|
||||
async def import_games_achievements_task(game_ids):
|
||||
try:
|
||||
await self.import_games_achievements(game_ids)
|
||||
finally:
|
||||
self.achievements_import_finished()
|
||||
self._achievements_import_in_progress = False
|
||||
|
||||
asyncio.create_task(import_games_achievements_task(game_ids))
|
||||
self._achievements_import_in_progress = True
|
||||
|
||||
async def import_games_achievements(self, game_ids):
|
||||
"""Call game_achievements_import_success/game_achievements_import_failure for each game_id on the list"""
|
||||
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)
|
||||
|
||||
imports = [import_game_achievements(game_id) for game_id in game_ids]
|
||||
await asyncio.gather(*imports)
|
||||
|
||||
async def get_local_games(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -319,6 +397,36 @@ class Plugin():
|
||||
async def get_game_times(self):
|
||||
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_task(game_ids):
|
||||
try:
|
||||
await self.import_game_times(game_ids)
|
||||
finally:
|
||||
self.game_times_import_finished()
|
||||
self._game_times_import_in_progress = False
|
||||
|
||||
asyncio.create_task(import_game_times_task(game_ids))
|
||||
self._game_times_import_in_progress = True
|
||||
|
||||
async def import_game_times(self, game_ids):
|
||||
"""Call game_time_import_success/game_time_import_failure for each game_id on the list"""
|
||||
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)
|
||||
|
||||
def create_and_run_plugin(plugin_class, argv):
|
||||
if len(argv) < 3:
|
||||
logging.critical("Not enough parameters, required: token, port")
|
||||
@@ -342,6 +450,8 @@ def create_and_run_plugin(plugin_class, argv):
|
||||
|
||||
async def coroutine():
|
||||
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)
|
||||
await plugin.run()
|
||||
|
||||
@@ -20,6 +20,7 @@ class NextStep():
|
||||
next_step: str
|
||||
auth_params: Dict[str, str]
|
||||
cookies: Optional[List[Cookie]] = None
|
||||
js: Optional[Dict[str, List[str]]] = None
|
||||
|
||||
@dataclass
|
||||
class LicenseInfo():
|
||||
@@ -36,7 +37,7 @@ class Dlc():
|
||||
class Game():
|
||||
game_id: str
|
||||
game_title: str
|
||||
dlcs: List[Dlc]
|
||||
dlcs: Optional[List[Dlc]]
|
||||
license_info: LicenseInfo
|
||||
|
||||
@dataclass
|
||||
@@ -68,6 +69,11 @@ class UserInfo():
|
||||
avatar_url: str
|
||||
presence: Presence
|
||||
|
||||
@dataclass
|
||||
class FriendInfo():
|
||||
user_id: str
|
||||
user_name: str
|
||||
|
||||
@dataclass
|
||||
class Room():
|
||||
room_id: str
|
||||
47
src/galaxy/http.py
Normal file
47
src/galaxy/http.py
Normal 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
|
||||
20
src/galaxy/tools.py
Normal file
20
src/galaxy/tools.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import io
|
||||
import os
|
||||
import zipfile
|
||||
from glob import glob
|
||||
|
||||
def zip_folder(folder):
|
||||
files = glob(os.path.join(folder, "**"), recursive=True)
|
||||
files = [file.replace(folder + os.sep, "") for file in files]
|
||||
files = [file for file in files if file]
|
||||
|
||||
zip_buffer = io.BytesIO()
|
||||
with zipfile.ZipFile(zip_buffer, mode="w", compression=zipfile.ZIP_DEFLATED) as zipf:
|
||||
for file in files:
|
||||
zipf.write(os.path.join(folder, file), arcname=file)
|
||||
return zip_buffer
|
||||
|
||||
def zip_folder_to_file(folder, filename):
|
||||
zip_content = zip_folder(folder).getbuffer()
|
||||
with open(filename, "wb") as archive:
|
||||
archive.write(zip_content)
|
||||
12
src/galaxy/unittest/mock.py
Normal file
12
src/galaxy/unittest/mock.py
Normal 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
|
||||
@@ -1,6 +0,0 @@
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
class AsyncMock(MagicMock):
|
||||
async def __call__(self, *args, **kwargs):
|
||||
# pylint: disable=useless-super-delegation
|
||||
return super(AsyncMock, self).__call__(*args, **kwargs)
|
||||
@@ -6,7 +6,7 @@ import pytest
|
||||
|
||||
from galaxy.api.plugin import Plugin
|
||||
from galaxy.api.consts import Platform
|
||||
from tests.async_mock import AsyncMock
|
||||
from galaxy.unittest.mock import AsyncMock, coroutine_mock
|
||||
|
||||
@pytest.fixture()
|
||||
def reader():
|
||||
@@ -57,7 +57,7 @@ def plugin(reader, writer):
|
||||
|
||||
with ExitStack() as stack:
|
||||
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:
|
||||
stack.enter_context(patch.object(Plugin, method))
|
||||
yield Plugin(Platform.Generic, "0.1", reader, writer, "token")
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import asyncio
|
||||
import json
|
||||
from unittest.mock import call
|
||||
|
||||
import pytest
|
||||
from pytest import raises
|
||||
|
||||
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():
|
||||
with raises(Exception):
|
||||
@@ -23,7 +26,7 @@ def test_success(plugin, readline, write):
|
||||
}
|
||||
}
|
||||
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_name="Got level 20", unlock_time=1548422395),
|
||||
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), ""]
|
||||
plugin.get_unlocked_achievements.side_effect = UnknownError()
|
||||
plugin.get_unlocked_achievements.coro.side_effect = UnknownError()
|
||||
asyncio.run(plugin.run())
|
||||
plugin.get_unlocked_achievements.assert_called()
|
||||
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()
|
||||
|
||||
@@ -18,7 +18,7 @@ def test_success(plugin, readline, write):
|
||||
}
|
||||
|
||||
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())
|
||||
plugin.authenticate.assert_called_with()
|
||||
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), ""]
|
||||
plugin.authenticate.side_effect = error()
|
||||
plugin.authenticate.coro.side_effect = error()
|
||||
asyncio.run(plugin.run())
|
||||
plugin.authenticate.assert_called_with()
|
||||
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), ""]
|
||||
plugin.authenticate.return_value = Authentication("132", "Zenek")
|
||||
plugin.authenticate.coro.return_value = Authentication("132", "Zenek")
|
||||
asyncio.run(plugin.run())
|
||||
plugin.authenticate.assert_called_with(stored_credentials={"token": "ABC"})
|
||||
write.assert_called()
|
||||
|
||||
@@ -21,7 +21,7 @@ def test_send_message_success(plugin, readline, write):
|
||||
}
|
||||
|
||||
readline.side_effect = [json.dumps(request), ""]
|
||||
plugin.send_message.return_value = None
|
||||
plugin.send_message.coro.return_value = None
|
||||
asyncio.run(plugin.run())
|
||||
plugin.send_message.assert_called_with(room_id="14", message="Hello!")
|
||||
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), ""]
|
||||
plugin.send_message.side_effect = error()
|
||||
plugin.send_message.coro.side_effect = error()
|
||||
asyncio.run(plugin.run())
|
||||
plugin.send_message.assert_called_with(room_id="15", message="Bye")
|
||||
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), ""]
|
||||
plugin.mark_as_read.return_value = None
|
||||
plugin.mark_as_read.coro.return_value = None
|
||||
asyncio.run(plugin.run())
|
||||
plugin.mark_as_read.assert_called_with(room_id="14", last_message_id="67")
|
||||
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), ""]
|
||||
plugin.mark_as_read.side_effect = error()
|
||||
plugin.mark_as_read.coro.side_effect = error()
|
||||
asyncio.run(plugin.run())
|
||||
plugin.mark_as_read.assert_called_with(room_id="18", last_message_id="7")
|
||||
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), ""]
|
||||
plugin.get_rooms.return_value = [
|
||||
plugin.get_rooms.coro.return_value = [
|
||||
Room("13", 0, None),
|
||||
Room("15", 34, "8")
|
||||
]
|
||||
@@ -170,7 +170,7 @@ def test_get_rooms_failure(plugin, readline, write):
|
||||
}
|
||||
|
||||
readline.side_effect = [json.dumps(request), ""]
|
||||
plugin.get_rooms.side_effect = UnknownError()
|
||||
plugin.get_rooms.coro.side_effect = UnknownError()
|
||||
asyncio.run(plugin.run())
|
||||
plugin.get_rooms.assert_called_with()
|
||||
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), ""]
|
||||
plugin.get_room_history_from_message.return_value = [
|
||||
plugin.get_room_history_from_message.coro.return_value = [
|
||||
Message("13", "149", 1549454837, "Hello"),
|
||||
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), ""]
|
||||
plugin.get_room_history_from_message.side_effect = error()
|
||||
plugin.get_room_history_from_message.coro.side_effect = error()
|
||||
asyncio.run(plugin.run())
|
||||
plugin.get_room_history_from_message.assert_called_with(room_id="33", message_id="88")
|
||||
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), ""]
|
||||
plugin.get_room_history_from_timestamp.return_value = [
|
||||
plugin.get_room_history_from_timestamp.coro.return_value = [
|
||||
Message("12", "155", 1549454836, "Bye")
|
||||
]
|
||||
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), ""]
|
||||
plugin.get_room_history_from_timestamp.side_effect = UnknownError()
|
||||
plugin.get_room_history_from_timestamp.coro.side_effect = UnknownError()
|
||||
asyncio.run(plugin.run())
|
||||
plugin.get_room_history_from_timestamp.assert_called_with(
|
||||
room_id="10",
|
||||
|
||||
90
tests/test_friends.py
Normal file
90
tests/test_friends.py
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import asyncio
|
||||
import json
|
||||
from unittest.mock import call
|
||||
|
||||
import pytest
|
||||
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):
|
||||
request = {
|
||||
@@ -12,7 +14,7 @@ def test_success(plugin, readline, write):
|
||||
}
|
||||
|
||||
readline.side_effect = [json.dumps(request), ""]
|
||||
plugin.get_game_times.return_value = [
|
||||
plugin.get_game_times.coro.return_value = [
|
||||
GameTime("3", 60, 1549550504),
|
||||
GameTime("5", 10, 1549550502)
|
||||
]
|
||||
@@ -47,7 +49,7 @@ def test_failure(plugin, readline, write):
|
||||
}
|
||||
|
||||
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())
|
||||
plugin.get_game_times.assert_called_with()
|
||||
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()
|
||||
|
||||
@@ -16,7 +16,7 @@ def test_success(plugin, readline, write):
|
||||
|
||||
readline.side_effect = [json.dumps(request), ""]
|
||||
|
||||
plugin.get_local_games.return_value = [
|
||||
plugin.get_local_games.coro.return_value = [
|
||||
LocalGame("1", LocalGameState.Running),
|
||||
LocalGame("2", LocalGameState.Installed),
|
||||
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), ""]
|
||||
plugin.get_local_games.side_effect = error()
|
||||
plugin.get_local_games.coro.side_effect = error()
|
||||
asyncio.run(plugin.run())
|
||||
plugin.get_local_games.assert_called_with()
|
||||
response = json.loads(write.call_args[0][0])
|
||||
|
||||
@@ -13,7 +13,7 @@ def test_success(plugin, readline, write):
|
||||
}
|
||||
|
||||
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(
|
||||
"5",
|
||||
@@ -75,7 +75,7 @@ def test_failure(plugin, readline, write):
|
||||
}
|
||||
|
||||
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())
|
||||
plugin.get_owned_games.assert_called_with()
|
||||
response = json.loads(write.call_args[0][0])
|
||||
|
||||
@@ -5,153 +5,6 @@ from galaxy.api.types import UserInfo, Presence
|
||||
from galaxy.api.errors import UnknownError
|
||||
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):
|
||||
request = {
|
||||
@@ -164,7 +17,7 @@ def test_get_users_success(plugin, readline, write):
|
||||
}
|
||||
|
||||
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))
|
||||
]
|
||||
asyncio.run(plugin.run())
|
||||
@@ -189,6 +42,7 @@ def test_get_users_success(plugin, readline, write):
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_users_failure(plugin, readline, write):
|
||||
request = {
|
||||
"jsonrpc": "2.0",
|
||||
@@ -200,7 +54,7 @@ def test_get_users_failure(plugin, readline, write):
|
||||
}
|
||||
|
||||
readline.side_effect = [json.dumps(request), ""]
|
||||
plugin.get_users.side_effect = UnknownError()
|
||||
plugin.get_users.coro.side_effect = UnknownError()
|
||||
asyncio.run(plugin.run())
|
||||
plugin.get_users.assert_called_with(user_id_list=["10", "11", "12"])
|
||||
response = json.loads(write.call_args[0][0])
|
||||
|
||||
Reference in New Issue
Block a user