Skip to content

Commit b518b2f

Browse files
Rasmus Oscar Welanderglpatcern
authored andcommitted
Added app API, example and tests
1 parent c7dfea3 commit b518b2f

File tree

3 files changed

+212
-0
lines changed

3 files changed

+212
-0
lines changed

examples/app_api_example.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""
2+
file_api_example.py
3+
4+
Example script to demonstrate the usage of the app API in the CS3Client class.
5+
note that these are examples, and is not meant to be run as a script.
6+
7+
Authors: Rasmus Welander, Diogo Castro, Giuseppe Lo Presti.
8+
9+
Last updated: 19/08/2024
10+
"""
11+
12+
import logging
13+
import configparser
14+
from cs3client import CS3Client
15+
from cs3resource import Resource
16+
17+
config = configparser.ConfigParser()
18+
with open("default.conf") as fdef:
19+
config.read_file(fdef)
20+
# log
21+
log = logging.getLogger(__name__)
22+
23+
24+
client = CS3Client(config, "cs3client", log)
25+
# client.auth.set_token("<your_token_here>")
26+
# OR
27+
client.auth.set_client_secret("<your_client_secret_here>")
28+
29+
print(client.auth.get_token())
30+
31+
# list_app_providers
32+
res = client.app.list_app_providers()
33+
if res is not None:
34+
print(res)
35+
36+
# open_in_app
37+
resource = Resource.from_file_ref_and_endpoint("/eos/user/r/rwelande/collabora.odt")
38+
res = client.app.open_in_app(resource)
39+
if res is not None:
40+
print(res)

src/app.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""
2+
app.py
3+
4+
Authors: Rasmus Welander, Diogo Castro, Giuseppe Lo Presti.
5+
6+
Last updated: 19/08/2024
7+
"""
8+
9+
import logging
10+
from auth import Auth
11+
from cs3resource import Resource
12+
import cs3.app.registry.v1beta1.registry_api_pb2 as cs3arreg
13+
import cs3.app.registry.v1beta1.resources_pb2 as cs3arres
14+
import cs3.gateway.v1beta1.gateway_api_pb2 as cs3gw
15+
import cs3.app.provider.v1beta1.resources_pb2 as cs3apr
16+
from cs3.gateway.v1beta1.gateway_api_pb2_grpc import GatewayAPIStub
17+
from statuscodehandler import StatusCodeHandler
18+
from config import Config
19+
20+
21+
class App:
22+
"""
23+
App class to handle app related API calls with CS3 Gateway API.
24+
"""
25+
26+
def __init__(
27+
self,
28+
config: Config,
29+
log: logging.Logger,
30+
gateway: GatewayAPIStub,
31+
auth: Auth,
32+
status_code_handler: StatusCodeHandler,
33+
) -> None:
34+
"""
35+
Initializes the App class with configuration, logger, auth, and gateway stub,
36+
37+
:param config: Config object containing the configuration parameters.
38+
:param log: Logger instance for logging.
39+
:param gateway: GatewayAPIStub instance for interacting with CS3 Gateway.
40+
:param auth: An instance of the auth class.
41+
:param status_code_handler: An instance of the StatusCodeHandler class.
42+
"""
43+
self._status_code_handler: StatusCodeHandler = status_code_handler
44+
self._gateway: GatewayAPIStub = gateway
45+
self._log: logging.Logger = log
46+
self._config: Config = config
47+
self._auth: Auth = auth
48+
49+
def open_in_app(self, resource: Resource, view_mode: str = None, app: str = None) -> cs3apr.OpenInAppURL:
50+
"""
51+
Open a file in an app, given the resource, view mode (VIEW_MODE_VIEW_ONLY, VIEW_MODE_READ_ONLY,
52+
VIEW_MODE_READ_WRITE, VIEW_MODE_PREVIEW), and app name.
53+
54+
:param resource: Resource object containing the resource information.
55+
:param view_mode: View mode of the app.
56+
:param app: App name.
57+
:return: URL to open the file in the app.
58+
:raises: AuthenticationException (Operation not permitted)
59+
:raises: NotFoundException (Resource not found)
60+
:raises: UnknownException (Unknown error)
61+
"""
62+
view_mode_type = None
63+
if view_mode:
64+
view_mode_type = cs3gw.OpenInAppRequest.ViewMode.Value(view_mode)
65+
req = cs3gw.OpenInAppRequest(ref=resource.ref, view_mode=view_mode_type, app=app)
66+
res = self._gateway.OpenInApp(request=req, metadata=[self._auth.get_token()])
67+
self._status_code_handler.handle_errors(res.status, "open in app", f"{resource.get_file_ref_str()}")
68+
self._log.debug(f'msg="Invoked OpenInApp" {resource.get_file_ref_str()} trace="{res.status.trace}"')
69+
return res.OpenInAppURL
70+
71+
def list_app_providers(self) -> list[cs3arres.ProviderInfo]:
72+
"""
73+
list_app_providers lists all the app providers.
74+
75+
:return: List of app providers.
76+
:raises: AuthenticationException (Operation not permitted)
77+
:raises: UnknownException (Unknown error)
78+
"""
79+
req = cs3arreg.ListAppProvidersRequest()
80+
res = self._gateway.ListAppProviders(request=req, metadata=[self._auth.get_token()])
81+
self._status_code_handler.handle_errors(res.status, "list app providers")
82+
self._log.debug(f'msg="Invoked ListAppProviders" res_count="{len(res.providers)}" trace="{res.status.trace}"')
83+
return res.providers

tests/test_app.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"""
2+
test_app.py
3+
4+
Tests that the App class methods work as expected.
5+
6+
Authors: Rasmus Welander, Diogo Castro, Giuseppe Lo Presti.
7+
8+
Last updated: 19/08/2024
9+
"""
10+
11+
from exceptions.exceptions import (
12+
AuthenticationException,
13+
NotFoundException,
14+
UnknownException,
15+
)
16+
17+
from cs3resource import Resource
18+
19+
import cs3.rpc.v1beta1.code_pb2 as cs3code
20+
from fixtures import ( # noqa: F401 (they are used, the framework is not detecting it)
21+
mock_config,
22+
mock_logger,
23+
mock_authentication,
24+
mock_gateway,
25+
app_instance,
26+
mock_status_code_handler,
27+
)
28+
29+
from unittest.mock import Mock, patch
30+
31+
import pytest
32+
33+
# Test cases for the App class
34+
# Test cases for the App class `list_app_providers` method using parameterized tests
35+
36+
37+
@pytest.mark.parametrize(
38+
"status_code, status_message, expected_exception, providers",
39+
[
40+
(cs3code.CODE_OK, None, None, ["provider1", "provider2"]),
41+
(cs3code.CODE_UNAUTHENTICATED, "error", AuthenticationException, None),
42+
(cs3code.CODE_INTERNAL, "error", UnknownException, None),
43+
],
44+
)
45+
def test_list_app_providers(
46+
app_instance, status_code, status_message, expected_exception, providers # noqa: F811 (not a redefinition)
47+
):
48+
mock_response = Mock()
49+
mock_response.status.code = status_code
50+
mock_response.status.message = status_message
51+
mock_response.providers = providers
52+
53+
with patch.object(app_instance._gateway, "ListAppProviders", return_value=mock_response):
54+
if expected_exception:
55+
with pytest.raises(expected_exception):
56+
app_instance.list_app_providers()
57+
else:
58+
result = app_instance.list_app_providers()
59+
assert result == providers
60+
61+
62+
@pytest.mark.parametrize(
63+
"status_code, status_message, expected_exception, open_in_app_url",
64+
[
65+
(cs3code.CODE_OK, None, None, "url"),
66+
(cs3code.CODE_UNAUTHENTICATED, "error", AuthenticationException, None),
67+
(cs3code.CODE_NOT_FOUND, "error", NotFoundException, None),
68+
(cs3code.CODE_INTERNAL, "error", UnknownException, None),
69+
],
70+
)
71+
def test_open_in_app(
72+
app_instance, status_code, status_message, expected_exception, open_in_app_url # noqa: F811 (not a redefinition)
73+
):
74+
resource = Resource.from_file_ref_and_endpoint(endpoint="", file="testfile")
75+
view_mode = "VIEW_MODE_VIEW_ONLY"
76+
app = "app"
77+
78+
mock_response = Mock()
79+
mock_response.status.code = status_code
80+
mock_response.status.message = status_message
81+
mock_response.OpenInAppURL = open_in_app_url
82+
83+
with patch.object(app_instance._gateway, "OpenInApp", return_value=mock_response):
84+
if expected_exception:
85+
with pytest.raises(expected_exception):
86+
app_instance.open_in_app(resource, view_mode, app)
87+
else:
88+
result = app_instance.open_in_app(resource, view_mode, app)
89+
assert result == open_in_app_url

0 commit comments

Comments
 (0)