From e9bc52a0abc96c101122be8ca7c0e4a649f0d9a5 Mon Sep 17 00:00:00 2001 From: Alexis Banaag Jr Date: Sat, 18 Nov 2023 19:02:42 +0800 Subject: [PATCH] feat: add support for batch authorization with service account --- .gitignore | 1 + pycronofy/client.py | 31 +++++++++------ pycronofy/tests/test_client.py | 72 ++++++++++++++++++++++++++++++++-- pyproject.toml | 5 +++ 4 files changed, 94 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 6ce3edf..7014c47 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ build MANIFEST dist *.egg-info +*.idea diff --git a/pycronofy/client.py b/pycronofy/client.py index 4ff5f14..127e6d7 100644 --- a/pycronofy/client.py +++ b/pycronofy/client.py @@ -493,7 +493,7 @@ def sequenced_availability(self, sequence=(), available_periods=()): :rtype: ``list`` """ - options = {} + options = dict() options['sequence'] = self.map_availability_sequence(sequence) self.translate_available_periods(available_periods) @@ -568,26 +568,33 @@ def upsert_event(self, calendar_id, event): self.request_handler.post( endpoint='calendars/%s/events' % calendar_id, data=event) - def authorize_with_service_account(self, email, scope, callback_url, state=None): + def authorize_with_service_account(self, email=None, scope=None, callback_url=None, service_account_authorizations=None, state=None): """ Attempts to authorize the email with impersonation from a service account :param string email: the email address to impersonate :param string callback_url: URL to callback with the OAuth code. :param string scope: The scope of the privileges you want the eventual access_token to grant. + :param string, optional state: A value that will be returned to you unaltered along with the authorization request decision. + :param list, optional service_account_authorizations: Allows a batch of 1 to 50 access requests to be submitted at the same time. :return: nothing """ - params = { - 'email': email, - 'scope': scope, - 'callback_url': callback_url - } - if state is not None: - params['state'] = state + if service_account_authorizations is not None: + params = { + "service_account_authorizations": service_account_authorizations + } + else: + params = { + 'email': email, + 'scope': scope, + 'callback_url': callback_url + } + + if state is not None: + params['state'] = state self.request_handler.post( endpoint="service_account_authorizations", data=params) - None def real_time_scheduling(self, availability, oauth, event, target_calendars=(), minimum_notice=None, callback_url=None, redirect_urls=None): """Generates an real time scheduling link to start the OAuth process with @@ -611,7 +618,7 @@ def real_time_scheduling(self, availability, oauth, event, target_calendars=(), :param dict event: - A dict describing the event :param list target_calendars: - An list of dics stating into which calendars to insert the created event - :param dict :minimum_notice - A dict describing the minimum notice for a booking (Optional) + :param dict, optional minimum_notice: - A dict describing the minimum notice for a booking (Optional) (DEPRECATED) :param string :callback_url - A String representing the URL Cronofy will notify once a slot has been selected. :param dict callback_url: - A dict containing redirect URLs for the end-user's journey @@ -732,7 +739,7 @@ def real_time_sequencing(self, availability, oauth, event, target_calendars=(), } if availability: - options = {} + options = dict() options['sequence'] = self.map_availability_sequence(availability.get('sequence', None)) if availability.get('available_periods', None): diff --git a/pycronofy/tests/test_client.py b/pycronofy/tests/test_client.py index 18246cf..3d8457c 100644 --- a/pycronofy/tests/test_client.py +++ b/pycronofy/tests/test_client.py @@ -3,6 +3,7 @@ import pytest import pytz import responses +from functools import partial from pycronofy import Client from pycronofy import settings from pycronofy.exceptions import PyCronofyRequestError @@ -743,7 +744,7 @@ def test_user_auth_link(client): @responses.activate def test_authorize_with_service_account(client): - """Test authorize_with_service_account with correct dat + """Test authorize_with_service_account with correct data :param Client client: Client instance with test data. """ @@ -755,15 +756,80 @@ def request_callback(request): assert payload['state'] == "state example" assert payload['callback_url'] == "http://www.example.com/callback" - return (202, {}, None) + return 202, {}, None + # Test with single service account authorization responses.add_callback( responses.POST, '%s/v1/service_account_authorizations' % settings.API_BASE_URL, callback=request_callback, content_type='application/json', ) - client.authorize_with_service_account("example@example.com", "felines", "http://www.example.com/callback", state="state example") + client.authorize_with_service_account( + email="example@example.com", + scope="felines", + callback_url="http://www.example.com/callback", + state="state example" + ) + + +@responses.activate +def test_authorize_with_service_account_batch(client): + """Test authorize_with_service_account with correct data + + :param Client client: Client instance with test data. + """ + + def request_callback(request, email, scope, state, callback_url): + request_body = json.loads(request.body) + + assert request_body['service_account_authorizations'] != [] + + service_account_authorizations = request_body['service_account_authorizations'] + for payload in service_account_authorizations: + assert payload['email'] == email + assert payload['scope'] == scope + assert payload['state'] == state + assert payload['callback_url'] == callback_url + + return 202, {}, None + + # Test with multiple service account authorizations + payloads = [ + { + 'email': 'example+1@example.com', + 'scope': 'felines', + 'state': 'state example', + 'callback_url': 'http://www.example.com/callback' + }, + { + 'email': 'example+2@example.com', + 'scope': 'felines', + 'state': 'state example', + 'callback_url': 'http://www.example.com/callback' + }, + { + 'email': 'example+3@example.com', + 'scope': 'felines', + 'state': 'state example', + 'callback_url': 'http://www.example.com/callback' + } + ] + + for data in payloads: + responses.add_callback( + responses.POST, + '%s/v1/service_account_authorizations' % settings.API_BASE_URL, + callback=partial( + request_callback, + email=data['email'], scope=data['scope'], callback_url=data['callback_url'], state=data['state'] + ), + content_type='application/json', + ) + + client.authorize_with_service_account( + service_account_authorizations=payloads + ) @responses.activate diff --git a/pyproject.toml b/pyproject.toml index 9a77548..f41a766 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,11 @@ classifiers = [ [tool.setuptools.dynamic] version = {attr = "pycronofy.__version__"} +[tool.pytest.ini_options] +pythonpath = [ + "." +] + [build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta"