mirror of
https://github.com/gogcom/galaxy-integrations-python-api.git
synced 2026-01-02 20:08:19 -05:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c874bc1d6e | ||
|
|
2dc56571d6 | ||
|
|
eb216a50a8 | ||
|
|
c9b1c8fcae | ||
|
|
a19a6cf11f | ||
|
|
98cff9cfb8 |
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="galaxy.plugin.api",
|
name="galaxy.plugin.api",
|
||||||
version="0.52",
|
version="0.54",
|
||||||
description="GOG Galaxy Integrations Python API",
|
description="GOG Galaxy Integrations Python API",
|
||||||
author='Galaxy team',
|
author='Galaxy team',
|
||||||
author_email='galaxy@gog.com',
|
author_email='galaxy@gog.com',
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ class Platform(Enum):
|
|||||||
Playfire = "playfire"
|
Playfire = "playfire"
|
||||||
Oculus = "oculus"
|
Oculus = "oculus"
|
||||||
Test = "test"
|
Test = "test"
|
||||||
|
Rockstar = "rockstar"
|
||||||
|
|
||||||
|
|
||||||
class Feature(Enum):
|
class Feature(Enum):
|
||||||
@@ -111,6 +112,7 @@ class Feature(Enum):
|
|||||||
ShutdownPlatformClient = "ShutdownPlatformClient"
|
ShutdownPlatformClient = "ShutdownPlatformClient"
|
||||||
LaunchPlatformClient = "LaunchPlatformClient"
|
LaunchPlatformClient = "LaunchPlatformClient"
|
||||||
ImportGameLibrarySettings = "ImportGameLibrarySettings"
|
ImportGameLibrarySettings = "ImportGameLibrarySettings"
|
||||||
|
ImportOSCompatibility = "ImportOSCompatibility"
|
||||||
|
|
||||||
|
|
||||||
class LicenseType(Enum):
|
class LicenseType(Enum):
|
||||||
@@ -129,3 +131,12 @@ class LocalGameState(Flag):
|
|||||||
None_ = 0
|
None_ = 0
|
||||||
Installed = 1
|
Installed = 1
|
||||||
Running = 2
|
Running = 2
|
||||||
|
|
||||||
|
|
||||||
|
class OSCompatibility(Flag):
|
||||||
|
"""Possible game OS compatibility.
|
||||||
|
Use "bitwise or" to express multiple OSs compatibility, e.g. ``os=OSCompatibility.Windows|OSCompatibility.MacOS``
|
||||||
|
"""
|
||||||
|
Windows = 0b001
|
||||||
|
MacOS = 0b010
|
||||||
|
Linux = 0b100
|
||||||
|
|||||||
@@ -129,8 +129,9 @@ class Server():
|
|||||||
await asyncio.sleep(0) # To not starve task queue
|
await asyncio.sleep(0) # To not starve task queue
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
logging.info("Closing JSON-RPC server - not more messages will be read")
|
if self._active:
|
||||||
self._active = False
|
logging.info("Closing JSON-RPC server - not more messages will be read")
|
||||||
|
self._active = False
|
||||||
|
|
||||||
async def wait_closed(self):
|
async def wait_closed(self):
|
||||||
await self._task_manager.wait()
|
await self._task_manager.wait()
|
||||||
@@ -281,6 +282,7 @@ class NotificationClient():
|
|||||||
self._send(notification)
|
self._send(notification)
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
|
self._task_manager.cancel()
|
||||||
await self._task_manager.wait()
|
await self._task_manager.wait()
|
||||||
|
|
||||||
def _send(self, data):
|
def _send(self, data):
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ import sys
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Dict, List, Optional, Set, Union
|
from typing import Any, Dict, List, Optional, Set, Union
|
||||||
|
|
||||||
from galaxy.api.consts import Feature
|
from galaxy.api.consts import Feature, OSCompatibility
|
||||||
from galaxy.api.errors import ImportInProgress, UnknownError
|
from galaxy.api.errors import ImportInProgress, UnknownError
|
||||||
from galaxy.api.jsonrpc import ApplicationError, NotificationClient, Server
|
from galaxy.api.jsonrpc import ApplicationError, NotificationClient, Server
|
||||||
from galaxy.api.types import Achievement, Authentication, FriendInfo, Game, GameTime, LocalGame, NextStep, GameLibrarySettings
|
from galaxy.api.types import Achievement, Authentication, FriendInfo, Game, GameTime, LocalGame, NextStep, GameLibrarySettings
|
||||||
from galaxy.task_manager import TaskManager
|
from galaxy.task_manager import TaskManager
|
||||||
|
|
||||||
|
|
||||||
class JSONEncoder(json.JSONEncoder):
|
class JSONEncoder(json.JSONEncoder):
|
||||||
def default(self, o): # pylint: disable=method-hidden
|
def default(self, o): # pylint: disable=method-hidden
|
||||||
if dataclasses.is_dataclass(o):
|
if dataclasses.is_dataclass(o):
|
||||||
@@ -47,6 +48,7 @@ class Plugin:
|
|||||||
self._achievements_import_in_progress = False
|
self._achievements_import_in_progress = False
|
||||||
self._game_times_import_in_progress = False
|
self._game_times_import_in_progress = False
|
||||||
self._game_library_settings_import_in_progress = False
|
self._game_library_settings_import_in_progress = False
|
||||||
|
self._os_compatibility_import_in_progress = False
|
||||||
|
|
||||||
self._persistent_cache = dict()
|
self._persistent_cache = dict()
|
||||||
|
|
||||||
@@ -113,6 +115,9 @@ class Plugin:
|
|||||||
self._register_method("start_game_library_settings_import", self._start_game_library_settings_import)
|
self._register_method("start_game_library_settings_import", self._start_game_library_settings_import)
|
||||||
self._detect_feature(Feature.ImportGameLibrarySettings, ["get_game_library_settings"])
|
self._detect_feature(Feature.ImportGameLibrarySettings, ["get_game_library_settings"])
|
||||||
|
|
||||||
|
self._register_method("start_os_compatibility_import", self._start_os_compatibility_import)
|
||||||
|
self._detect_feature(Feature.ImportOSCompatibility, ["get_os_compatibility"])
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -173,11 +178,13 @@ class Plugin:
|
|||||||
def _wrap_external_method(self, handler, name: str):
|
def _wrap_external_method(self, handler, name: str):
|
||||||
async def wrapper(*args, **kwargs):
|
async def wrapper(*args, **kwargs):
|
||||||
return await self._external_task_manager.create_task(handler(*args, **kwargs), name, False)
|
return await self._external_task_manager.create_task(handler(*args, **kwargs), name, False)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""Plugin's main coroutine."""
|
"""Plugin's main coroutine."""
|
||||||
await self._server.run()
|
await self._server.run()
|
||||||
|
logging.debug("Plugin run loop finished")
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
if not self._active:
|
if not self._active:
|
||||||
@@ -190,10 +197,12 @@ class Plugin:
|
|||||||
self._active = False
|
self._active = False
|
||||||
|
|
||||||
async def wait_closed(self) -> None:
|
async def wait_closed(self) -> None:
|
||||||
|
logging.debug("Waiting for plugin to close")
|
||||||
await self._external_task_manager.wait()
|
await self._external_task_manager.wait()
|
||||||
await self._internal_task_manager.wait()
|
await self._internal_task_manager.wait()
|
||||||
await self._server.wait_closed()
|
await self._server.wait_closed()
|
||||||
await self._notification_client.close()
|
await self._notification_client.close()
|
||||||
|
logging.debug("Plugin closed")
|
||||||
|
|
||||||
def create_task(self, coro, description):
|
def create_task(self, coro, description):
|
||||||
"""Wrapper around asyncio.create_task - takes care of canceling tasks on shutdown"""
|
"""Wrapper around asyncio.create_task - takes care of canceling tasks on shutdown"""
|
||||||
@@ -420,6 +429,27 @@ class Plugin:
|
|||||||
def _game_library_settings_import_finished(self) -> None:
|
def _game_library_settings_import_finished(self) -> None:
|
||||||
self._notification_client.notify("game_library_settings_import_finished", None)
|
self._notification_client.notify("game_library_settings_import_finished", None)
|
||||||
|
|
||||||
|
def _os_compatibility_import_success(self, game_id: str, os_compatibility: Optional[OSCompatibility]) -> None:
|
||||||
|
self._notification_client.notify(
|
||||||
|
"os_compatibility_import_success",
|
||||||
|
{
|
||||||
|
"game_id": game_id,
|
||||||
|
"os_compatibility": os_compatibility
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _os_compatibility_import_failure(self, game_id: str, error: ApplicationError) -> None:
|
||||||
|
self._notification_client.notify(
|
||||||
|
"os_compatibility_import_failure",
|
||||||
|
{
|
||||||
|
"game_id": game_id,
|
||||||
|
"error": error.json()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _os_compatibility_import_finished(self) -> None:
|
||||||
|
self._notification_client.notify("os_compatibility_import_finished", None)
|
||||||
|
|
||||||
def lost_authentication(self) -> None:
|
def lost_authentication(self) -> None:
|
||||||
"""Notify the client that integration has lost authentication for the
|
"""Notify the client that integration has lost authentication for the
|
||||||
current user and is unable to perform actions which would require it.
|
current user and is unable to perform actions which would require it.
|
||||||
@@ -805,7 +835,7 @@ class Plugin:
|
|||||||
This allows for optimizations like batch requests to platform API.
|
This allows for optimizations like batch requests to platform API.
|
||||||
Default implementation returns None.
|
Default implementation returns None.
|
||||||
|
|
||||||
:param game_ids: the ids of the games for which game time are imported
|
:param game_ids: the ids of the games for which game library settings are imported
|
||||||
:return: context
|
:return: context
|
||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
@@ -815,17 +845,74 @@ class Plugin:
|
|||||||
identified by the provided game_id.
|
identified by the provided game_id.
|
||||||
This method is called by import task initialized by GOG Galaxy Client.
|
This method is called by import task initialized by GOG Galaxy Client.
|
||||||
|
|
||||||
:param game_id: the id of the game for which the game time is returned
|
:param game_id: the id of the game for which the game library settings are imported
|
||||||
:param context: the value returned from :meth:`prepare_game_library_settings_context`
|
:param context: the value returned from :meth:`prepare_game_library_settings_context`
|
||||||
:return: GameLibrarySettings object
|
:return: GameLibrarySettings object
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def game_library_settings_import_complete(self) -> None:
|
def game_library_settings_import_complete(self) -> None:
|
||||||
"""Override this method to handle operations after game times import is finished
|
"""Override this method to handle operations after game library settings import is finished
|
||||||
(like updating cache).
|
(like updating cache).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
async def _start_os_compatibility_import(self, game_ids: List[str]) -> None:
|
||||||
|
if self._os_compatibility_import_in_progress:
|
||||||
|
raise ImportInProgress()
|
||||||
|
|
||||||
|
context = await self.prepare_os_compatibility_context(game_ids)
|
||||||
|
|
||||||
|
async def import_os_compatibility(game_id, context_):
|
||||||
|
try:
|
||||||
|
os_compatibility = await self.get_os_compatibility(game_id, context_)
|
||||||
|
self._os_compatibility_import_success(game_id, os_compatibility)
|
||||||
|
except ApplicationError as error:
|
||||||
|
self._os_compatibility_import_failure(game_id, error)
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Unexpected exception raised in import_os_compatibility")
|
||||||
|
self._os_compatibility_import_failure(game_id, UnknownError())
|
||||||
|
|
||||||
|
async def import_os_compatibility_set(game_ids_, context_):
|
||||||
|
try:
|
||||||
|
await asyncio.gather(*[
|
||||||
|
import_os_compatibility(game_id, context_) for game_id in game_ids_
|
||||||
|
])
|
||||||
|
finally:
|
||||||
|
self._os_compatibility_import_finished()
|
||||||
|
self._os_compatibility_import_in_progress = False
|
||||||
|
self.os_compatibility_import_complete()
|
||||||
|
|
||||||
|
self._external_task_manager.create_task(
|
||||||
|
import_os_compatibility_set(game_ids, context),
|
||||||
|
"game OS compatibility import",
|
||||||
|
handle_exceptions=False
|
||||||
|
)
|
||||||
|
self._os_compatibility_import_in_progress = True
|
||||||
|
|
||||||
|
async def prepare_os_compatibility_context(self, game_ids: List[str]) -> Any:
|
||||||
|
"""Override this method to prepare context for get_os_compatibility.
|
||||||
|
This allows for optimizations like batch requests to platform API.
|
||||||
|
Default implementation returns None.
|
||||||
|
|
||||||
|
:param game_ids: the ids of the games for which game os compatibility is imported
|
||||||
|
:return: context
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_os_compatibility(self, game_id: str, context: Any) -> Optional[OSCompatibility]:
|
||||||
|
"""Override this method to return the OS compatibility for the game with the provided game_id.
|
||||||
|
This method is called by import task initialized by GOG Galaxy Client.
|
||||||
|
|
||||||
|
:param game_id: the id of the game for which the game os compatibility is imported
|
||||||
|
:param context: the value returned from :meth:`prepare_os_compatibility_context`
|
||||||
|
:return: OSCompatibility flags indicating compatible OSs, or None if compatibility is not know
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def os_compatibility_import_complete(self) -> None:
|
||||||
|
"""Override this method to handle operations after OS compatibility import is finished (like updating cache)."""
|
||||||
|
|
||||||
|
|
||||||
def create_and_run_plugin(plugin_class, argv):
|
def create_and_run_plugin(plugin_class, argv):
|
||||||
"""Call this method as an entry point for the implemented integration.
|
"""Call this method as an entry point for the implemented integration.
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import List, Dict, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from galaxy.api.consts import LicenseType, LocalGameState
|
from galaxy.api.consts import LicenseType, LocalGameState
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Authentication():
|
class Authentication:
|
||||||
"""Return this from :meth:`.authenticate` or :meth:`.pass_login_credentials`
|
"""Return this from :meth:`.authenticate` or :meth:`.pass_login_credentials`
|
||||||
to inform the client that authentication has successfully finished.
|
to inform the client that authentication has successfully finished.
|
||||||
|
|
||||||
@@ -14,8 +15,9 @@ class Authentication():
|
|||||||
user_id: str
|
user_id: str
|
||||||
user_name: str
|
user_name: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Cookie():
|
class Cookie:
|
||||||
"""Cookie
|
"""Cookie
|
||||||
|
|
||||||
:param name: name of the cookie
|
:param name: name of the cookie
|
||||||
@@ -28,8 +30,9 @@ class Cookie():
|
|||||||
domain: Optional[str] = None
|
domain: Optional[str] = None
|
||||||
path: Optional[str] = None
|
path: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class NextStep():
|
class NextStep:
|
||||||
"""Return this from :meth:`.authenticate` or :meth:`.pass_login_credentials` to open client built-in browser with given url.
|
"""Return this from :meth:`.authenticate` or :meth:`.pass_login_credentials` to open client built-in browser with given url.
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
@@ -67,8 +70,9 @@ class NextStep():
|
|||||||
cookies: Optional[List[Cookie]] = None
|
cookies: Optional[List[Cookie]] = None
|
||||||
js: Optional[Dict[str, List[str]]] = None
|
js: Optional[Dict[str, List[str]]] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LicenseInfo():
|
class LicenseInfo:
|
||||||
"""Information about the license of related product.
|
"""Information about the license of related product.
|
||||||
|
|
||||||
:param license_type: type of license
|
:param license_type: type of license
|
||||||
@@ -77,8 +81,9 @@ class LicenseInfo():
|
|||||||
license_type: LicenseType
|
license_type: LicenseType
|
||||||
owner: Optional[str] = None
|
owner: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Dlc():
|
class Dlc:
|
||||||
"""Downloadable content object.
|
"""Downloadable content object.
|
||||||
|
|
||||||
:param dlc_id: id of the dlc
|
:param dlc_id: id of the dlc
|
||||||
@@ -89,8 +94,9 @@ class Dlc():
|
|||||||
dlc_title: str
|
dlc_title: str
|
||||||
license_info: LicenseInfo
|
license_info: LicenseInfo
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Game():
|
class Game:
|
||||||
"""Game object.
|
"""Game object.
|
||||||
|
|
||||||
:param game_id: unique identifier of the game, this will be passed as parameter for methods such as launch_game
|
:param game_id: unique identifier of the game, this will be passed as parameter for methods such as launch_game
|
||||||
@@ -103,8 +109,9 @@ class Game():
|
|||||||
dlcs: Optional[List[Dlc]]
|
dlcs: Optional[List[Dlc]]
|
||||||
license_info: LicenseInfo
|
license_info: LicenseInfo
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Achievement():
|
class Achievement:
|
||||||
"""Achievement, has to be initialized with either id or name.
|
"""Achievement, has to be initialized with either id or name.
|
||||||
|
|
||||||
:param unlock_time: unlock time of the achievement
|
:param unlock_time: unlock time of the achievement
|
||||||
@@ -119,8 +126,9 @@ class Achievement():
|
|||||||
assert self.achievement_id or self.achievement_name, \
|
assert self.achievement_id or self.achievement_name, \
|
||||||
"One of achievement_id or achievement_name is required"
|
"One of achievement_id or achievement_name is required"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LocalGame():
|
class LocalGame:
|
||||||
"""Game locally present on the authenticated user's computer.
|
"""Game locally present on the authenticated user's computer.
|
||||||
|
|
||||||
:param game_id: id of the game
|
:param game_id: id of the game
|
||||||
@@ -129,8 +137,9 @@ class LocalGame():
|
|||||||
game_id: str
|
game_id: str
|
||||||
local_game_state: LocalGameState
|
local_game_state: LocalGameState
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FriendInfo():
|
class FriendInfo:
|
||||||
"""Information about a friend of the currently authenticated user.
|
"""Information about a friend of the currently authenticated user.
|
||||||
|
|
||||||
:param user_id: id of the user
|
:param user_id: id of the user
|
||||||
@@ -139,8 +148,9 @@ class FriendInfo():
|
|||||||
user_id: str
|
user_id: str
|
||||||
user_name: str
|
user_name: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GameTime():
|
class GameTime:
|
||||||
"""Game time of a game, defines the total time spent in the game
|
"""Game time of a game, defines the total time spent in the game
|
||||||
and the last time the game was played.
|
and the last time the game was played.
|
||||||
|
|
||||||
@@ -152,8 +162,9 @@ class GameTime():
|
|||||||
time_played: Optional[int]
|
time_played: Optional[int]
|
||||||
last_played_time: Optional[int]
|
last_played_time: Optional[int]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GameLibrarySettings():
|
class GameLibrarySettings:
|
||||||
"""Library settings of a game, defines assigned tags and visibility flag.
|
"""Library settings of a game, defines assigned tags and visibility flag.
|
||||||
|
|
||||||
:param game_id: id of the related game
|
:param game_id: id of the related game
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ def create_tcp_connector(*args, **kwargs) -> aiohttp.TCPConnector:
|
|||||||
ssl_context.load_verify_locations(certifi.where())
|
ssl_context.load_verify_locations(certifi.where())
|
||||||
kwargs.setdefault("ssl", ssl_context)
|
kwargs.setdefault("ssl", ssl_context)
|
||||||
kwargs.setdefault("limit", DEFAULT_LIMIT)
|
kwargs.setdefault("limit", DEFAULT_LIMIT)
|
||||||
return aiohttp.TCPConnector(*args, **kwargs) # type: ignore due to https://github.com/python/mypy/issues/4001
|
# due to https://github.com/python/mypy/issues/4001
|
||||||
|
return aiohttp.TCPConnector(*args, **kwargs) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def create_client_session(*args, **kwargs) -> aiohttp.ClientSession:
|
def create_client_session(*args, **kwargs) -> aiohttp.ClientSession:
|
||||||
@@ -103,7 +104,8 @@ def create_client_session(*args, **kwargs) -> aiohttp.ClientSession:
|
|||||||
kwargs.setdefault("connector", create_tcp_connector())
|
kwargs.setdefault("connector", create_tcp_connector())
|
||||||
kwargs.setdefault("timeout", aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT))
|
kwargs.setdefault("timeout", aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT))
|
||||||
kwargs.setdefault("raise_for_status", True)
|
kwargs.setdefault("raise_for_status", True)
|
||||||
return aiohttp.ClientSession(*args, **kwargs) # type: ignore due to https://github.com/python/mypy/issues/4001
|
# due to https://github.com/python/mypy/issues/4001
|
||||||
|
return aiohttp.ClientSession(*args, **kwargs) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ async def plugin(reader, writer):
|
|||||||
"get_game_library_settings",
|
"get_game_library_settings",
|
||||||
"prepare_game_library_settings_context",
|
"prepare_game_library_settings_context",
|
||||||
"game_library_settings_import_complete",
|
"game_library_settings_import_complete",
|
||||||
|
"get_os_compatibility",
|
||||||
|
"prepare_os_compatibility_context",
|
||||||
|
"os_compatibility_import_complete",
|
||||||
)
|
)
|
||||||
|
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ def test_base_class():
|
|||||||
Feature.ImportFriends,
|
Feature.ImportFriends,
|
||||||
Feature.ShutdownPlatformClient,
|
Feature.ShutdownPlatformClient,
|
||||||
Feature.LaunchPlatformClient,
|
Feature.LaunchPlatformClient,
|
||||||
Feature.ImportGameLibrarySettings
|
Feature.ImportGameLibrarySettings,
|
||||||
|
Feature.ImportOSCompatibility
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from tests import create_message, get_messages
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_game_time_success(plugin, read, write):
|
async def test_get_library_settings_success(plugin, read, write):
|
||||||
plugin.prepare_game_library_settings_context.return_value = async_return_value("abc")
|
plugin.prepare_game_library_settings_context.return_value = async_return_value("abc")
|
||||||
request = {
|
request = {
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
|
|||||||
187
tests/test_os_compatibility.py
Normal file
187
tests/test_os_compatibility.py
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
from unittest.mock import call
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from galaxy.api.consts import OSCompatibility
|
||||||
|
from galaxy.api.errors import BackendError
|
||||||
|
from galaxy.unittest.mock import async_return_value
|
||||||
|
|
||||||
|
from tests import create_message, get_messages
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_os_compatibility_success(plugin, read, write):
|
||||||
|
context = "abc"
|
||||||
|
plugin.prepare_os_compatibility_context.return_value = async_return_value(context)
|
||||||
|
request = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": "11",
|
||||||
|
"method": "start_os_compatibility_import",
|
||||||
|
"params": {"game_ids": ["666", "13", "42"]}
|
||||||
|
}
|
||||||
|
read.side_effect = [async_return_value(create_message(request)), async_return_value(b"", 10)]
|
||||||
|
plugin.get_os_compatibility.side_effect = [
|
||||||
|
async_return_value(OSCompatibility.Linux),
|
||||||
|
async_return_value(None),
|
||||||
|
async_return_value(OSCompatibility.Windows | OSCompatibility.MacOS),
|
||||||
|
]
|
||||||
|
await plugin.run()
|
||||||
|
plugin.get_os_compatibility.assert_has_calls([
|
||||||
|
call("666", context),
|
||||||
|
call("13", context),
|
||||||
|
call("42", context),
|
||||||
|
])
|
||||||
|
plugin.os_compatibility_import_complete.assert_called_once_with()
|
||||||
|
|
||||||
|
assert get_messages(write) == [
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": "11",
|
||||||
|
"result": None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "os_compatibility_import_success",
|
||||||
|
"params": {
|
||||||
|
"game_id": "666",
|
||||||
|
"os_compatibility": OSCompatibility.Linux.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "os_compatibility_import_success",
|
||||||
|
"params": {
|
||||||
|
"game_id": "13",
|
||||||
|
"os_compatibility": None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "os_compatibility_import_success",
|
||||||
|
"params": {
|
||||||
|
"game_id": "42",
|
||||||
|
"os_compatibility": (OSCompatibility.Windows | OSCompatibility.MacOS).value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "os_compatibility_import_finished",
|
||||||
|
"params": None
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.parametrize("exception,code,message", [
|
||||||
|
(BackendError, 4, "Backend error"),
|
||||||
|
(KeyError, 0, "Unknown error")
|
||||||
|
])
|
||||||
|
async def test_get_os_compatibility_error(exception, code, message, plugin, read, write):
|
||||||
|
game_id = "6"
|
||||||
|
request_id = "55"
|
||||||
|
plugin.prepare_os_compatibility_context.return_value = async_return_value(None)
|
||||||
|
request = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": request_id,
|
||||||
|
"method": "start_os_compatibility_import",
|
||||||
|
"params": {"game_ids": [game_id]}
|
||||||
|
}
|
||||||
|
read.side_effect = [async_return_value(create_message(request)), async_return_value(b"", 10)]
|
||||||
|
plugin.get_os_compatibility.side_effect = exception
|
||||||
|
await plugin.run()
|
||||||
|
plugin.get_os_compatibility.assert_called()
|
||||||
|
plugin.os_compatibility_import_complete.assert_called_once_with()
|
||||||
|
|
||||||
|
assert get_messages(write) == [
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": request_id,
|
||||||
|
"result": None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "os_compatibility_import_failure",
|
||||||
|
"params": {
|
||||||
|
"game_id": game_id,
|
||||||
|
"error": {
|
||||||
|
"code": code,
|
||||||
|
"message": message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "os_compatibility_import_finished",
|
||||||
|
"params": None
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_prepare_get_os_compatibility_context_error(plugin, read, write):
|
||||||
|
request_id = "31415"
|
||||||
|
plugin.prepare_os_compatibility_context.side_effect = BackendError()
|
||||||
|
request = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": request_id,
|
||||||
|
"method": "start_os_compatibility_import",
|
||||||
|
"params": {"game_ids": ["6"]}
|
||||||
|
}
|
||||||
|
read.side_effect = [async_return_value(create_message(request)), async_return_value(b"", 10)]
|
||||||
|
await plugin.run()
|
||||||
|
|
||||||
|
assert get_messages(write) == [
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": request_id,
|
||||||
|
"error": {
|
||||||
|
"code": 4,
|
||||||
|
"message": "Backend error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_import_already_in_progress_error(plugin, read, write):
|
||||||
|
plugin.prepare_os_compatibility_context.return_value = async_return_value(None)
|
||||||
|
requests = [
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": "3",
|
||||||
|
"method": "start_os_compatibility_import",
|
||||||
|
"params": {
|
||||||
|
"game_ids": ["42"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": "4",
|
||||||
|
"method": "start_os_compatibility_import",
|
||||||
|
"params": {
|
||||||
|
"game_ids": ["666"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
read.side_effect = [
|
||||||
|
async_return_value(create_message(requests[0])),
|
||||||
|
async_return_value(create_message(requests[1])),
|
||||||
|
async_return_value(b"", 10)
|
||||||
|
]
|
||||||
|
|
||||||
|
await plugin.run()
|
||||||
|
|
||||||
|
responses = get_messages(write)
|
||||||
|
assert {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": "3",
|
||||||
|
"result": None
|
||||||
|
} in responses
|
||||||
|
assert {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": "4",
|
||||||
|
"error": {
|
||||||
|
"code": 600,
|
||||||
|
"message": "Import already in progress"
|
||||||
|
}
|
||||||
|
} in responses
|
||||||
|
|
||||||
Reference in New Issue
Block a user