Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions graphene_linked_events/rest_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@


class LinkedEventsApiClient(object):
CONNECTION_TIMEOUT = 20

def __init__(self, config) -> None:
self.root = config["ROOT"]
self.api_key = config["API_KEY"]
self.data_source = config["DATA_SOURCE"]
self.timeout = config.get("TIMEOUT", 60)
super().__init__()

def get_actions(self, resource=None):
Expand All @@ -34,13 +33,13 @@ def retrieve(self, resource, id, params=None, is_event_staff=False):
params=formatted_params,
headers=headers,
cookies=cookies,
timeout=self.CONNECTION_TIMEOUT,
timeout=self.timeout,
)
return requests.request(
actions["retrieve"]["method"],
actions["retrieve"]["url"].format(id),
params=formatted_params,
timeout=self.CONNECTION_TIMEOUT,
timeout=self.timeout,
)

def list(self, resource, filter_list=None, is_event_staff=False):
Expand All @@ -61,13 +60,13 @@ def list(self, resource, filter_list=None, is_event_staff=False):
params=filter_params,
headers=headers,
cookies=cookies,
timeout=self.CONNECTION_TIMEOUT,
timeout=self.timeout,
)
return requests.request(
actions["list"]["method"],
actions["list"]["url"],
params=filter_params,
timeout=self.CONNECTION_TIMEOUT,
timeout=self.timeout,
)

def create(self, resource, body):
Expand All @@ -78,7 +77,7 @@ def create(self, resource, body):
actions["create"]["url"],
data=body,
headers=headers,
timeout=self.CONNECTION_TIMEOUT,
timeout=self.timeout,
)

def update(self, resource, id, body):
Expand All @@ -89,7 +88,7 @@ def update(self, resource, id, body):
actions["update"]["url"].format(id),
data=body,
headers=headers,
timeout=self.CONNECTION_TIMEOUT,
timeout=self.timeout,
)

def delete(self, resource, id):
Expand All @@ -99,7 +98,7 @@ def delete(self, resource, id):
actions["delete"]["method"],
actions["delete"]["url"].format(id),
headers=headers,
timeout=self.CONNECTION_TIMEOUT,
timeout=self.timeout,
)

# Special action to full-text search generic resources
Expand All @@ -110,7 +109,7 @@ def search(self, params):
action["method"],
action["url"],
params=search_params,
timeout=self.CONNECTION_TIMEOUT,
timeout=self.timeout,
)
return response

Expand All @@ -123,7 +122,7 @@ def upload(self, resource, body, files):
data=body,
files=files,
headers=headers,
timeout=self.CONNECTION_TIMEOUT,
timeout=self.timeout,
)

@staticmethod
Expand Down
161 changes: 161 additions & 0 deletions graphene_linked_events/tests/test_rest_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
from unittest.mock import Mock, patch

from graphene_linked_events.rest_client import LinkedEventsApiClient


class TestLinkedEventsApiClientTimeout:
"""Tests for LinkedEventsApiClient timeout configuration."""

def get_base_config(self, timeout=None):
"""Return a base config dict, optionally with timeout."""
config = {
"ROOT": "https://api.example.com/v1/",
"API_KEY": "test-api-key",
"DATA_SOURCE": "test-source",
}
if timeout is not None:
config["TIMEOUT"] = timeout
return config

def test_timeout_from_config(self):
"""Client should use timeout value from config."""
config = self.get_base_config(timeout=90)
client = LinkedEventsApiClient(config)
assert client.timeout == 90

def test_timeout_default_when_not_in_config(self):
"""Client should use default timeout (60s) when not in config."""
config = self.get_base_config()
client = LinkedEventsApiClient(config)
assert client.timeout == 60

def test_timeout_zero_is_allowed(self):
"""Client should accept zero timeout from config."""
config = self.get_base_config(timeout=0)
client = LinkedEventsApiClient(config)
assert client.timeout == 0

@patch("graphene_linked_events.rest_client.requests.request")
def test_retrieve_uses_configured_timeout(self, mock_request):
"""Retrieve method should pass configured timeout to requests."""
mock_request.return_value = Mock(status_code=200)
config = self.get_base_config(timeout=45)
client = LinkedEventsApiClient(config)

client.retrieve("event", "123")

mock_request.assert_called_once()
assert mock_request.call_args.kwargs["timeout"] == 45

@patch("graphene_linked_events.rest_client.requests.request")
def test_list_uses_configured_timeout(self, mock_request):
"""List method should pass configured timeout to requests."""
mock_request.return_value = Mock(status_code=200)
config = self.get_base_config(timeout=45)
client = LinkedEventsApiClient(config)

client.list("event")

mock_request.assert_called_once()
assert mock_request.call_args.kwargs["timeout"] == 45

@patch("graphene_linked_events.rest_client.requests.request")
def test_create_uses_configured_timeout(self, mock_request):
"""Create method should pass configured timeout to requests."""
mock_request.return_value = Mock(status_code=201)
config = self.get_base_config(timeout=45)
client = LinkedEventsApiClient(config)

client.create("event", '{"name": "Test"}')

mock_request.assert_called_once()
assert mock_request.call_args.kwargs["timeout"] == 45

@patch("graphene_linked_events.rest_client.requests.request")
def test_update_uses_configured_timeout(self, mock_request):
"""Update method should pass configured timeout to requests."""
mock_request.return_value = Mock(status_code=200)
config = self.get_base_config(timeout=45)
client = LinkedEventsApiClient(config)

client.update("event", "123", '{"name": "Updated"}')

mock_request.assert_called_once()
assert mock_request.call_args.kwargs["timeout"] == 45

@patch("graphene_linked_events.rest_client.requests.request")
def test_delete_uses_configured_timeout(self, mock_request):
"""Delete method should pass configured timeout to requests."""
mock_request.return_value = Mock(status_code=204)
config = self.get_base_config(timeout=45)
client = LinkedEventsApiClient(config)

client.delete("event", "123")

mock_request.assert_called_once()
assert mock_request.call_args.kwargs["timeout"] == 45

@patch("graphene_linked_events.rest_client.requests.request")
def test_search_uses_configured_timeout(self, mock_request):
"""Search method should pass configured timeout to requests."""
mock_request.return_value = Mock(status_code=200)
config = self.get_base_config(timeout=45)
client = LinkedEventsApiClient(config)

# Mock get_actions to return a valid search action
# (search is a special action that doesn't use resource param)
with patch.object(
client,
"get_actions",
return_value={"search": {"method": "GET", "url": client.root + "search/"}},
):
client.search({"q": "test"})

mock_request.assert_called_once()
assert mock_request.call_args.kwargs["timeout"] == 45

@patch("graphene_linked_events.rest_client.requests.request")
def test_upload_uses_configured_timeout(self, mock_request):
"""Upload method should pass configured timeout to requests."""
mock_request.return_value = Mock(status_code=201)
config = self.get_base_config(timeout=45)
client = LinkedEventsApiClient(config)

client.upload("image", {"name": "test"}, {"file": b"data"})

mock_request.assert_called_once()
assert mock_request.call_args.kwargs["timeout"] == 45


class TestLinkedEventsApiClientConfig:
"""Tests for LinkedEventsApiClient configuration."""

def test_client_stores_root_url(self):
"""Client should store root URL from config."""
config = {
"ROOT": "https://api.example.com/v1/",
"API_KEY": "test-key",
"DATA_SOURCE": "test-source",
}
client = LinkedEventsApiClient(config)
assert client.root == "https://api.example.com/v1/"

def test_client_stores_api_key(self):
"""Client should store API key from config."""
config = {
"ROOT": "https://api.example.com/v1/",
"API_KEY": "my-secret-key",
"DATA_SOURCE": "test-source",
}
client = LinkedEventsApiClient(config)
assert client.api_key == "my-secret-key"

def test_client_stores_data_source(self):
"""Client should store data source from config."""
config = {
"ROOT": "https://api.example.com/v1/",
"API_KEY": "test-key",
"DATA_SOURCE": "palvelutarjotin",
}
client = LinkedEventsApiClient(config)
assert client.data_source == "palvelutarjotin"
2 changes: 2 additions & 0 deletions palvelutarjotin/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
ENABLE_GRAPHIQL=(bool, False),
LINKED_EVENTS_API_ROOT=(str, "https://api.hel.fi/linkedevents/v1/"),
LINKED_EVENTS_API_KEY=(str, ""),
LINKED_EVENTS_API_TIMEOUT=(int, 60),
Comment thread
voneiden marked this conversation as resolved.
LINKED_EVENTS_DATA_SOURCE=(str, "palvelutarjotin"),
SERVICEMAP_API_ROOT=(str, "https://www.hel.fi/palvelukarttaws/rest/v4/unit/"),
NOTIFICATION_SERVICE_SMS_ENABLED=(bool, True),
Expand Down Expand Up @@ -432,6 +433,7 @@ def sentry_traces_sampler(sampling_context: SamplingContext) -> float:
"ROOT": env.str("LINKED_EVENTS_API_ROOT"),
"API_KEY": env.str("LINKED_EVENTS_API_KEY"),
"DATA_SOURCE": env.str("LINKED_EVENTS_DATA_SOURCE"),
"TIMEOUT": env.int("LINKED_EVENTS_API_TIMEOUT"),
}

SERVICEMAP_API_CONFIG = {"ROOT": env.str("SERVICEMAP_API_ROOT")}
Expand Down