From 83c91f8d72fe60e9316eda7c2bb88383934a99fb Mon Sep 17 00:00:00 2001 From: Bartosz Prokop Date: Fri, 26 Jan 2024 14:47:18 +0100 Subject: [PATCH] VerificationAPI --- .github/workflows/run-tests.yml | 8 +- sinch/core/clients/sinch_client_async.py | 18 +-- sinch/core/clients/sinch_client_base.py | 32 ++-- .../clients/sinch_client_configuration.py | 7 +- sinch/core/clients/sinch_client_sync.py | 18 +-- sinch/core/endpoint.py | 3 + sinch/core/enums.py | 1 + sinch/core/ports/http_transport.py | 38 ++++- sinch/core/signature.py | 58 +++++++ sinch/domains/verification/__init__.py | 152 ++++++++++++++++++ .../verification/endpoints/__init__.py | 0 .../endpoints/get_verification_by_id.py | 35 ++++ .../endpoints/get_verification_by_identity.py | 36 +++++ .../get_verification_by_reference.py | 35 ++++ .../endpoints/report_verification_using_id.py | 39 +++++ .../report_verification_using_identity.py | 39 +++++ .../endpoints/start_verification.py | 50 ++++++ .../endpoints/verification_endpoint.py | 13 ++ sinch/domains/verification/enums.py | 17 ++ sinch/domains/verification/exceptions.py | 5 + sinch/domains/verification/models/__init__.py | 0 sinch/domains/verification/models/requests.py | 40 +++++ .../domains/verification/models/responses.py | 74 +++++++++ tests/conftest.py | 93 +++++++---- .../verification/test_get_report_using_id.py | 23 +++ .../test_get_report_using_identity.py | 23 +++ .../test_get_report_using_reference.py | 19 +++ .../test_report_verification_using_id.py | 35 ++++ ...test_report_verification_using_identity.py | 33 ++++ .../verification/test_start_verification.py | 53 ++++++ tests/integration/test_logging.py | 11 +- tests/integration/test_request_signing.py | 60 +++++++ tests/integration/test_token_refresh.py | 17 +- 33 files changed, 1004 insertions(+), 81 deletions(-) create mode 100644 sinch/core/signature.py create mode 100644 sinch/domains/verification/__init__.py create mode 100644 sinch/domains/verification/endpoints/__init__.py create mode 100644 sinch/domains/verification/endpoints/get_verification_by_id.py create mode 100644 sinch/domains/verification/endpoints/get_verification_by_identity.py create mode 100644 sinch/domains/verification/endpoints/get_verification_by_reference.py create mode 100644 sinch/domains/verification/endpoints/report_verification_using_id.py create mode 100644 sinch/domains/verification/endpoints/report_verification_using_identity.py create mode 100644 sinch/domains/verification/endpoints/start_verification.py create mode 100644 sinch/domains/verification/endpoints/verification_endpoint.py create mode 100644 sinch/domains/verification/enums.py create mode 100644 sinch/domains/verification/exceptions.py create mode 100644 sinch/domains/verification/models/__init__.py create mode 100644 sinch/domains/verification/models/requests.py create mode 100644 sinch/domains/verification/models/responses.py create mode 100644 tests/e2e/verification/test_get_report_using_id.py create mode 100644 tests/e2e/verification/test_get_report_using_identity.py create mode 100644 tests/e2e/verification/test_get_report_using_reference.py create mode 100644 tests/e2e/verification/test_report_verification_using_id.py create mode 100644 tests/e2e/verification/test_report_verification_using_identity.py create mode 100644 tests/e2e/verification/test_start_verification.py create mode 100644 tests/integration/test_request_signing.py diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 1c6cf88..0d4b61e 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -21,7 +21,13 @@ env: APP_ID: ${{ secrets.APP_ID }} EMPTY_PROJECT_ID: ${{ secrets.EMPTY_PROJECT_ID }} TEMPLATES_ORIGIN: ${{ secrets.TEMPLATES_ORIGIN }} - + APPLICATION_SECRET: ${{ secrets.APPLICATION_SECRET }} + APPLICATION_KEY: ${{ secrets.APPLICATION_KEY }} + VERIFICATION_ID: ${{ secrets.VERIFICATION_ID }} + VERIFICATION_ORIGIN: ${{ secrets.VERIFICATION_ORIGIN}} + VERIFICATION_REQUEST_SIGNATURE_TIMESTAMP: ${{ secrets.VERIFICATION_REQUEST_SIGNATURE_TIMESTAMP}} + VERIFICATION_REQUEST_WITH_EMPTY_BODY_SIGNATURE: ${{ secrets.VERIFICATION_REQUEST_WITH_EMPTY_BODY_SIGNATURE}} + VERIFICATION_REQUEST_SIGNATURE: ${{ secrets.VERIFICATION_REQUEST_SIGNATURE}} jobs: build: diff --git a/sinch/core/clients/sinch_client_async.py b/sinch/core/clients/sinch_client_async.py index 4f1fd83..a32a223 100644 --- a/sinch/core/clients/sinch_client_async.py +++ b/sinch/core/clients/sinch_client_async.py @@ -6,6 +6,7 @@ from sinch.domains.numbers import NumbersAsync from sinch.domains.conversation import ConversationAsync from sinch.domains.sms import SMSAsync +from sinch.domains.verification import Verification as VerificationAsync class ClientAsync(ClientBase): @@ -20,15 +21,10 @@ def __init__( key_secret, project_id, logger_name=None, - logger=None + logger=None, + application_key: str = None, + application_secret: str = None ): - super().__init__( - key_id=key_id, - key_secret=key_secret, - project_id=project_id, - logger_name=logger_name, - logger=logger - ) self.configuration = Configuration( key_id=key_id, key_secret=key_secret, @@ -36,9 +32,13 @@ def __init__( logger_name=logger_name, logger=logger, transport=HTTPTransportAioHTTP(self), - token_manager=TokenManagerAsync(self) + token_manager=TokenManagerAsync(self), + application_secret=application_secret, + application_key=application_key ) + self.authentication = AuthenticationAsync(self) self.numbers = NumbersAsync(self) self.conversation = ConversationAsync(self) self.sms = SMSAsync(self) + self.verification = VerificationAsync(self) diff --git a/sinch/core/clients/sinch_client_base.py b/sinch/core/clients/sinch_client_base.py index 559558b..7f53fd0 100644 --- a/sinch/core/clients/sinch_client_base.py +++ b/sinch/core/clients/sinch_client_base.py @@ -1,5 +1,4 @@ -from abc import ABC -from sinch.core.exceptions import ValidationException +from abc import ABC, abstractmethod from sinch.core.clients.sinch_client_configuration import Configuration from sinch.domains.authentication import AuthenticationBase from sinch.domains.numbers import NumbersBase @@ -10,32 +9,27 @@ class ClientBase(ABC): """ Sinch abstract base class for concrete Sinch Client implementations. - By default this SDK provides two implementations - sync and async. + By default, this SDK provides two implementations - sync and async. Feel free to utilize any of them for you custom implementation. """ + configuration = Configuration + authentication = AuthenticationBase + numbers = NumbersBase + conversation = ConversationBase + sms = SMSBase + + @abstractmethod def __init__( self, key_id, key_secret, project_id, logger_name=None, - logger=None + logger=None, + application_key: str = None, + application_secret: str = None ): - if not key_id or not key_secret or not project_id: - raise ValidationException( - message=( - "key_id, key_secret and project_id are required by the Sinch Client. " - "Those credentials can be obtained from Sinch portal." - ), - is_from_server=False, - response=None - ) - - self.configuration = Configuration - self.authentication = AuthenticationBase - self.numbers = NumbersBase - self.conversation = ConversationBase - self.sms = SMSBase + pass def __repr__(self): return f"Sinch SDK client for project_id: {self.configuration.project_id}" diff --git a/sinch/core/clients/sinch_client_configuration.py b/sinch/core/clients/sinch_client_configuration.py index 86736b4..74f0931 100644 --- a/sinch/core/clients/sinch_client_configuration.py +++ b/sinch/core/clients/sinch_client_configuration.py @@ -19,14 +19,19 @@ def __init__( logger=None, logger_name: str = None, disable_https=False, - connection_timeout=10 + connection_timeout=10, + application_key: str = None, + application_secret: str = None ): self.key_id = key_id self.key_secret = key_secret self.project_id = project_id + self.application_key = application_key + self.application_secret = application_secret self.connection_timeout = connection_timeout self.auth_origin = "auth.sinch.com" self.numbers_origin = "numbers.api.sinch.com" + self.verification_origin = "verification.api.sinch.com" self._conversation_region = "eu" self._conversation_domain = ".conversation.api.sinch.com" self._sms_region = "us" diff --git a/sinch/core/clients/sinch_client_sync.py b/sinch/core/clients/sinch_client_sync.py index ab05e5e..0ec939c 100644 --- a/sinch/core/clients/sinch_client_sync.py +++ b/sinch/core/clients/sinch_client_sync.py @@ -6,6 +6,7 @@ from sinch.domains.numbers import Numbers from sinch.domains.conversation import Conversation from sinch.domains.sms import SMS +from sinch.domains.verification import Verification class Client(ClientBase): @@ -20,15 +21,10 @@ def __init__( key_secret, project_id, logger_name=None, - logger=None + logger=None, + application_key: str = None, + application_secret: str = None ): - super().__init__( - key_id=key_id, - key_secret=key_secret, - project_id=project_id, - logger_name=logger_name, - logger=logger - ) self.configuration = Configuration( key_id=key_id, key_secret=key_secret, @@ -36,9 +32,13 @@ def __init__( logger_name=logger_name, logger=logger, transport=HTTPTransportRequests(self), - token_manager=TokenManager(self) + token_manager=TokenManager(self), + application_key=application_key, + application_secret=application_secret ) + self.authentication = Authentication(self) self.numbers = Numbers(self) self.conversation = Conversation(self) self.sms = SMS(self) + self.verification = Verification(self) diff --git a/sinch/core/endpoint.py b/sinch/core/endpoint.py index c252370..d11608b 100644 --- a/sinch/core/endpoint.py +++ b/sinch/core/endpoint.py @@ -10,6 +10,9 @@ class HTTPEndpoint(ABC): def __init__(self, project_id, request_data): pass + def get_url_without_origin(self, sinch): + return '/' + '/'.join(self.build_url(sinch).split('/')[1:]) + def build_url(self, sinch): return diff --git a/sinch/core/enums.py b/sinch/core/enums.py index 66879fc..c1d63ee 100644 --- a/sinch/core/enums.py +++ b/sinch/core/enums.py @@ -12,3 +12,4 @@ class HTTPMethods(Enum): class HTTPAuthentication(Enum): BASIC = "BASIC" OAUTH = "OAUTH" + SIGNED = "SIGNED" diff --git a/sinch/core/ports/http_transport.py b/sinch/core/ports/http_transport.py index d848947..06ff055 100644 --- a/sinch/core/ports/http_transport.py +++ b/sinch/core/ports/http_transport.py @@ -2,8 +2,10 @@ from abc import ABC, abstractmethod from platform import python_version from sinch.core.endpoint import HTTPEndpoint +from sinch.core.signature import Signature from sinch.core.models.http_request import HttpRequest from sinch.core.models.http_response import HTTPResponse +from sinch.core.exceptions import ValidationException from sinch.core.enums import HTTPAuthentication from sinch.core.token_manager import TokenState from sinch import __version__ as sdk_version @@ -18,6 +20,21 @@ def request(self, endpoint: HTTPEndpoint) -> HTTPResponse: pass def authenticate(self, endpoint, request_data): + if endpoint.HTTP_AUTHENTICATION in (HTTPAuthentication.BASIC.value, HTTPAuthentication.OAUTH.value): + if ( + not self.sinch.configuration.key_id + or not self.sinch.configuration.key_secret + or not self.sinch.configuration.project_id + ): + raise ValidationException( + message=( + "key_id, key_secret and project_id are required by this API. " + "Those credentials can be obtained from Sinch portal." + ), + is_from_server=False, + response=None + ) + if endpoint.HTTP_AUTHENTICATION == HTTPAuthentication.BASIC.value: request_data.auth = (self.sinch.configuration.key_id, self.sinch.configuration.key_secret) else: @@ -29,6 +46,23 @@ def authenticate(self, endpoint, request_data): "Authorization": f"Bearer {token}", "Content-Type": "application/json" }) + elif endpoint.HTTP_AUTHENTICATION == HTTPAuthentication.SIGNED.value: + if not self.sinch.configuration.application_key or not self.sinch.configuration.application_secret: + raise ValidationException( + message=( + "application key and application secret are required by this API. " + "Those credentials can be obtained from Sinch portal." + ), + is_from_server=False, + response=None + ) + signature = Signature( + self.sinch, + endpoint.HTTP_METHOD, + request_data.request_body, + endpoint.get_url_without_origin(self.sinch) + ) + request_data.headers = signature.get_http_headers_with_signature() return request_data @@ -50,7 +84,7 @@ def prepare_request(self, endpoint: HTTPEndpoint) -> HttpRequest: ) def handle_response(self, endpoint: HTTPEndpoint, http_response: HTTPResponse): - if http_response.status_code == 401: + if http_response.status_code == 401 and endpoint.HTTP_AUTHENTICATION == HTTPAuthentication.OAUTH.value: self.sinch.configuration.token_manager.handle_invalid_token(http_response) if self.sinch.configuration.token_manager.token_state == TokenState.EXPIRED: return self.request(endpoint=endpoint) @@ -78,7 +112,7 @@ async def authenticate(self, endpoint, request_data): return request_data async def handle_response(self, endpoint: HTTPEndpoint, http_response: HTTPResponse): - if http_response.status_code == 401: + if http_response.status_code == 401 and endpoint.HTTP_AUTHENTICATION == HTTPAuthentication.OAUTH.value: self.sinch.configuration.token_manager.handle_invalid_token(http_response) if self.sinch.configuration.token_manager.token_state == TokenState.EXPIRED: return await self.request(endpoint=endpoint) diff --git a/sinch/core/signature.py b/sinch/core/signature.py new file mode 100644 index 0000000..5e45626 --- /dev/null +++ b/sinch/core/signature.py @@ -0,0 +1,58 @@ +import hashlib +import hmac +import base64 +from datetime import datetime, timezone + + +class Signature: + def __init__( + self, + sinch, + http_method, + request_data, + request_uri, + content_type=None, + signature_timestamp=None + ): + self.sinch = sinch + self.http_method = http_method + self.content_type = content_type or 'application/json; charset=UTF-8' + self.request_data = request_data + self.signature_timestamp = signature_timestamp or datetime.now(timezone.utc).isoformat() + self.request_uri = request_uri + self.authorization_signature = None + + def get_http_headers_with_signature(self): + if not self.authorization_signature: + self.calculate() + + return { + "Content-Type": self.content_type, + "Authorization": ( + f"Application {self.sinch.configuration.application_key}:{self.authorization_signature}" + ), + "x-timestamp": self.signature_timestamp + } + + def calculate(self): + b64_decoded_application_secret = base64.b64decode(self.sinch.configuration.application_secret) + if self.request_data: + encoded_verification_request = hashlib.md5(self.request_data.encode()) + encoded_verification_request = base64.b64encode(encoded_verification_request.digest()) + + else: + encoded_verification_request = ''.encode() + + request_timestamp = "x-timestamp:" + self.signature_timestamp + + string_to_sign = ( + self.http_method + '\n' + + encoded_verification_request.decode() + '\n' + + self.content_type + '\n' + + request_timestamp + '\n' + + self.request_uri + ) + + self.authorization_signature = base64.b64encode( + hmac.new(b64_decoded_application_secret, string_to_sign.encode(), hashlib.sha256).digest() + ).decode() diff --git a/sinch/domains/verification/__init__.py b/sinch/domains/verification/__init__.py new file mode 100644 index 0000000..e32a8b2 --- /dev/null +++ b/sinch/domains/verification/__init__.py @@ -0,0 +1,152 @@ +from sinch.domains.verification.endpoints.start_verification import StartVerificationEndpoint +from sinch.domains.verification.endpoints.report_verification_using_identity import ( + ReportVerificationByIdentityEndpoint +) +from sinch.domains.verification.endpoints.report_verification_using_id import ( + ReportVerificationByIdEndpoint +) +from sinch.domains.verification.endpoints.get_verification_by_id import ( + GetVerificationStatusByIdEndpoint +) +from sinch.domains.verification.endpoints.get_verification_by_identity import ( + GetVerificationStatusByIdentityEndpoint +) +from sinch.domains.verification.endpoints.get_verification_by_reference import ( + GetVerificationStatusByReferenceEndpoint +) +from sinch.domains.verification.models.responses import ( + StartVerificationResponse, + ReportVerificationByIdentityResponse, + ReportVerificationByIdResponse, + GetVerificationStatusByIdentityResponse, + GetVerificationStatusByIdResponse, + GetVerificationStatusByReferenceResponse +) +from sinch.domains.verification.models.requests import ( + StartVerificationRequest, + ReportVerificationByIdentityRequest, + ReportVerificationByIdRequest, + GetVerificationStatusByIdRequest, + GetVerificationStatusByIdentityRequest, + GetVerificationStatusByReferenceRequest +) + +from sinch.domains.verification.enums import VerificationMethod + + +class Verifications: + def __init__(self, sinch): + self._sinch = sinch + + def start( + self, + identity: dict, + method: VerificationMethod, + reference: str = None, + custom: str = None, + flash_call_options: dict = None + ) -> StartVerificationResponse: + return self._sinch.configuration.transport.request( + StartVerificationEndpoint( + request_data=StartVerificationRequest( + identity=identity, + method=method, + reference=reference, + custom=custom, + flash_call_options=flash_call_options + ) + ) + ) + + def report_by_id( + self, + id: str, + verification_report_request: dict + ) -> ReportVerificationByIdResponse: + return self._sinch.configuration.transport.request( + ReportVerificationByIdEndpoint( + request_data=ReportVerificationByIdRequest( + id, + verification_report_request + ) + ) + ) + + def report_by_identity( + self, + endpoint, + verification_report_request + ) -> ReportVerificationByIdentityResponse: + return self._sinch.configuration.transport.request( + ReportVerificationByIdentityEndpoint( + request_data=ReportVerificationByIdentityRequest( + endpoint, + verification_report_request + ) + ) + ) + + +class VerificationStatus: + def __init__(self, sinch): + self._sinch = sinch + + def get_by_reference(self, reference) -> GetVerificationStatusByReferenceResponse: + return self._sinch.configuration.transport.request( + GetVerificationStatusByReferenceEndpoint( + request_data=GetVerificationStatusByReferenceRequest( + reference=reference + ) + ) + ) + + def get_by_id(self, id) -> GetVerificationStatusByIdResponse: + return self._sinch.configuration.transport.request( + GetVerificationStatusByIdEndpoint( + request_data=GetVerificationStatusByIdRequest( + id=id + ) + ) + ) + + def get_by_identity(self, endpoint, method) -> GetVerificationStatusByIdentityResponse: + return self._sinch.configuration.transport.request( + GetVerificationStatusByIdentityEndpoint( + request_data=GetVerificationStatusByIdentityRequest( + endpoint=endpoint, + method=method + ) + ) + ) + + +class VerificationBase: + """ + Documentation for the Verification API: https://developers.sinch.com/docs/verification/ + """ + def __init__(self, sinch): + self._sinch = sinch + + +class Verification(VerificationBase): + """ + Synchronous version of the Verification Domain + """ + __doc__ += VerificationBase.__doc__ + + def __init__(self, sinch): + super(Verification, self).__init__(sinch) + self.verifications = Verifications(self._sinch) + self.verification_status = VerificationStatus(self._sinch) + + +class VerificationAsync(VerificationBase): + """ + Asynchronous version of the Verification Domain + """ + __doc__ += VerificationBase.__doc__ + + def __init__(self, sinch): + super(VerificationAsync, self).__init__(sinch) + self.verifications = Verifications(self._sinch) + self.verification_status = VerificationStatus(self._sinch) diff --git a/sinch/domains/verification/endpoints/__init__.py b/sinch/domains/verification/endpoints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sinch/domains/verification/endpoints/get_verification_by_id.py b/sinch/domains/verification/endpoints/get_verification_by_id.py new file mode 100644 index 0000000..131e1e7 --- /dev/null +++ b/sinch/domains/verification/endpoints/get_verification_by_id.py @@ -0,0 +1,35 @@ +from sinch.core.models.http_response import HTTPResponse +from sinch.domains.verification.endpoints.verification_endpoint import VerificationEndpoint +from sinch.core.enums import HTTPAuthentication, HTTPMethods +from sinch.domains.verification.models.requests import GetVerificationStatusByIdRequest +from sinch.domains.verification.models.responses import GetVerificationStatusByIdResponse + + +class GetVerificationStatusByIdEndpoint(VerificationEndpoint): + ENDPOINT_URL = "{origin}/verification/v1/verifications/id/{id}" + HTTP_METHOD = HTTPMethods.GET.value + HTTP_AUTHENTICATION = HTTPAuthentication.SIGNED.value + + def __init__(self, request_data: GetVerificationStatusByIdRequest): + self.request_data = request_data + + def build_url(self, sinch): + return self.ENDPOINT_URL.format( + origin=sinch.configuration.verification_origin, + id=self.request_data.id + ) + + def handle_response(self, response: HTTPResponse) -> GetVerificationStatusByIdResponse: + super().handle_response(response) + return GetVerificationStatusByIdResponse( + id=response.body.get("id"), + method=response.body.get("method"), + status=response.body.get("status"), + price=response.body.get("price"), + identity=response.body.get("identity"), + country_id=response.body.get("country_id"), + verification_timestamp=response.body.get("verification_timestamp"), + reference=response.body.get("reference"), + reason=response.body.get("reason"), + call_complete=response.body.get("call_complete") + ) diff --git a/sinch/domains/verification/endpoints/get_verification_by_identity.py b/sinch/domains/verification/endpoints/get_verification_by_identity.py new file mode 100644 index 0000000..88008df --- /dev/null +++ b/sinch/domains/verification/endpoints/get_verification_by_identity.py @@ -0,0 +1,36 @@ +from sinch.core.models.http_response import HTTPResponse +from sinch.domains.verification.endpoints.verification_endpoint import VerificationEndpoint +from sinch.core.enums import HTTPAuthentication, HTTPMethods +from sinch.domains.verification.models.requests import GetVerificationStatusByIdentityRequest +from sinch.domains.verification.models.responses import GetVerificationStatusByIdentityResponse + + +class GetVerificationStatusByIdentityEndpoint(VerificationEndpoint): + ENDPOINT_URL = "{origin}/verification/v1/verifications/{method}/number/{endpoint}" + HTTP_METHOD = HTTPMethods.GET.value + HTTP_AUTHENTICATION = HTTPAuthentication.SIGNED.value + + def __init__(self, request_data: GetVerificationStatusByIdentityRequest): + self.request_data = request_data + + def build_url(self, sinch): + return self.ENDPOINT_URL.format( + origin=sinch.configuration.verification_origin, + method=self.request_data.method, + endpoint=self.request_data.endpoint + ) + + def handle_response(self, response: HTTPResponse) -> GetVerificationStatusByIdentityResponse: + super().handle_response(response) + return GetVerificationStatusByIdentityResponse( + id=response.body.get("id"), + method=response.body.get("method"), + status=response.body.get("status"), + price=response.body.get("price"), + identity=response.body.get("identity"), + country_id=response.body.get("country_id"), + verification_timestamp=response.body.get("verification_timestamp"), + reference=response.body.get("reference"), + reason=response.body.get("reason"), + call_complete=response.body.get("call_complete") + ) diff --git a/sinch/domains/verification/endpoints/get_verification_by_reference.py b/sinch/domains/verification/endpoints/get_verification_by_reference.py new file mode 100644 index 0000000..3a5115b --- /dev/null +++ b/sinch/domains/verification/endpoints/get_verification_by_reference.py @@ -0,0 +1,35 @@ +from sinch.core.models.http_response import HTTPResponse +from sinch.domains.verification.endpoints.verification_endpoint import VerificationEndpoint +from sinch.core.enums import HTTPAuthentication, HTTPMethods +from sinch.domains.verification.models.requests import GetVerificationStatusByReferenceRequest +from sinch.domains.verification.models.responses import GetVerificationStatusByReferenceResponse + + +class GetVerificationStatusByReferenceEndpoint(VerificationEndpoint): + ENDPOINT_URL = "{origin}/verification/v1/verifications/reference/{reference}" + HTTP_METHOD = HTTPMethods.GET.value + HTTP_AUTHENTICATION = HTTPAuthentication.SIGNED.value + + def __init__(self, request_data: GetVerificationStatusByReferenceRequest): + self.request_data = request_data + + def build_url(self, sinch): + return self.ENDPOINT_URL.format( + origin=sinch.configuration.verification_origin, + reference=self.request_data.reference + ) + + def handle_response(self, response: HTTPResponse) -> GetVerificationStatusByReferenceResponse: + super().handle_response(response) + return GetVerificationStatusByReferenceResponse( + id=response.body.get("id"), + method=response.body.get("method"), + status=response.body.get("status"), + price=response.body.get("price"), + identity=response.body.get("identity"), + country_id=response.body.get("country_id"), + verification_timestamp=response.body.get("verification_timestamp"), + reference=response.body.get("reference"), + reason=response.body.get("reason"), + call_complete=response.body.get("call_complete") + ) diff --git a/sinch/domains/verification/endpoints/report_verification_using_id.py b/sinch/domains/verification/endpoints/report_verification_using_id.py new file mode 100644 index 0000000..63bbb9e --- /dev/null +++ b/sinch/domains/verification/endpoints/report_verification_using_id.py @@ -0,0 +1,39 @@ +import json +from sinch.core.models.http_response import HTTPResponse +from sinch.domains.verification.endpoints.verification_endpoint import VerificationEndpoint +from sinch.core.enums import HTTPAuthentication, HTTPMethods +from sinch.domains.verification.models.requests import ReportVerificationByIdRequest +from sinch.domains.verification.models.responses import ReportVerificationByIdResponse + + +class ReportVerificationByIdEndpoint(VerificationEndpoint): + ENDPOINT_URL = "{origin}/verification/v1/verifications/id/{id}" + HTTP_METHOD = HTTPMethods.PUT.value + HTTP_AUTHENTICATION = HTTPAuthentication.SIGNED.value + + def __init__(self, request_data: ReportVerificationByIdRequest): + self.request_data = request_data + + def build_url(self, sinch): + return self.ENDPOINT_URL.format( + origin=sinch.configuration.verification_origin, + id=self.request_data.id + ) + + def request_body(self): + return json.dumps(self.request_data.verification_report_request) + + def handle_response(self, response: HTTPResponse) -> ReportVerificationByIdResponse: + super().handle_response(response) + return ReportVerificationByIdResponse( + id=response.body.get("id"), + method=response.body.get("method"), + status=response.body.get("status"), + price=response.body.get("price"), + identity=response.body.get("identity"), + country_id=response.body.get("country_id"), + verification_timestamp=response.body.get("verification_timestamp"), + reference=response.body.get("reference"), + reason=response.body.get("reason"), + call_complete=response.body.get("call_complete") + ) diff --git a/sinch/domains/verification/endpoints/report_verification_using_identity.py b/sinch/domains/verification/endpoints/report_verification_using_identity.py new file mode 100644 index 0000000..dbc4287 --- /dev/null +++ b/sinch/domains/verification/endpoints/report_verification_using_identity.py @@ -0,0 +1,39 @@ +import json +from sinch.core.models.http_response import HTTPResponse +from sinch.domains.verification.endpoints.verification_endpoint import VerificationEndpoint +from sinch.core.enums import HTTPAuthentication, HTTPMethods +from sinch.domains.verification.models.requests import ReportVerificationByIdentityRequest +from sinch.domains.verification.models.responses import ReportVerificationByIdentityResponse + + +class ReportVerificationByIdentityEndpoint(VerificationEndpoint): + ENDPOINT_URL = "{origin}/verification/v1/verifications/number/{endpoint}" + HTTP_METHOD = HTTPMethods.PUT.value + HTTP_AUTHENTICATION = HTTPAuthentication.SIGNED.value + + def __init__(self, request_data: ReportVerificationByIdentityRequest): + self.request_data = request_data + + def build_url(self, sinch): + return self.ENDPOINT_URL.format( + origin=sinch.configuration.verification_origin, + endpoint=self.request_data.endpoint + ) + + def request_body(self): + return json.dumps(self.request_data.verification_report_request) + + def handle_response(self, response: HTTPResponse) -> ReportVerificationByIdentityResponse: + super().handle_response(response) + return ReportVerificationByIdentityResponse( + id=response.body.get("id"), + method=response.body.get("method"), + status=response.body.get("status"), + price=response.body.get("price"), + identity=response.body.get("identity"), + country_id=response.body.get("country_id"), + verification_timestamp=response.body.get("verification_timestamp"), + reference=response.body.get("reference"), + reason=response.body.get("reason"), + call_complete=response.body.get("call_complete") + ) diff --git a/sinch/domains/verification/endpoints/start_verification.py b/sinch/domains/verification/endpoints/start_verification.py new file mode 100644 index 0000000..5902203 --- /dev/null +++ b/sinch/domains/verification/endpoints/start_verification.py @@ -0,0 +1,50 @@ +from sinch.core.models.http_response import HTTPResponse +from sinch.domains.verification.endpoints.verification_endpoint import VerificationEndpoint +from sinch.core.enums import HTTPAuthentication, HTTPMethods +from sinch.domains.verification.enums import VerificationMethod +from sinch.domains.verification.models.requests import StartVerificationRequest +from sinch.domains.verification.models.responses import ( + StartVerificationResponse, + StartSMSInitiateVerificationResponse, + StartDataInitiateVerificationResponse, + StartCalloutInitiateVerificationResponse, + StartFlashCallInitiateVerificationResponse +) + + +class StartVerificationEndpoint(VerificationEndpoint): + ENDPOINT_URL = "{origin}/verification/v1/verifications" + HTTP_METHOD = HTTPMethods.POST.value + HTTP_AUTHENTICATION = HTTPAuthentication.SIGNED.value + + def __init__(self, request_data: StartVerificationRequest): + self.request_data = request_data + + def build_url(self, sinch): + return self.ENDPOINT_URL.format( + origin=sinch.configuration.verification_origin, + ) + + def request_body(self): + return self.request_data.as_json() + + def handle_response(self, response: HTTPResponse) -> StartVerificationResponse: + if self.request_data.method == VerificationMethod.SMS.value: + return StartSMSInitiateVerificationResponse( + **response.body + ) + elif self.request_data.method == VerificationMethod.FLASHCALL.value: + return StartFlashCallInitiateVerificationResponse( + id=response.body.get("id"), + method=response.body.get("method"), + _links=response.body.get("_links"), + flashcall=response.body.get("flashCall") + ) + elif self.request_data.method == VerificationMethod.CALLOUT.value: + return StartCalloutInitiateVerificationResponse( + **response.body + ) + elif self.request_data.method == VerificationMethod.SEAMLESS.value: + return StartDataInitiateVerificationResponse( + **response.body + ) diff --git a/sinch/domains/verification/endpoints/verification_endpoint.py b/sinch/domains/verification/endpoints/verification_endpoint.py new file mode 100644 index 0000000..e7898b6 --- /dev/null +++ b/sinch/domains/verification/endpoints/verification_endpoint.py @@ -0,0 +1,13 @@ +from sinch.core.models.http_response import HTTPResponse +from sinch.core.endpoint import HTTPEndpoint +from sinch.domains.verification.exceptions import VerificationException + + +class VerificationEndpoint(HTTPEndpoint): + def handle_response(self, response: HTTPResponse): + if response.status_code >= 400: + raise VerificationException( + message=response.body["message"], + response=response, + is_from_server=True + ) diff --git a/sinch/domains/verification/enums.py b/sinch/domains/verification/enums.py new file mode 100644 index 0000000..82f6c80 --- /dev/null +++ b/sinch/domains/verification/enums.py @@ -0,0 +1,17 @@ +from enum import Enum + + +class VerificationMethod(Enum): + SMS = "sms" + FLASHCALL = "flashcall" + CALLOUT = "callout" + SEAMLESS = "seamless" + + +class VerificationStatus(Enum): + PENDING = "PENDING" + SUCCESSFUL = "SUCCESSFUL" + FAIL = "FAIL" + DENIED = "DENIED" + ABORTED = "ABORTED" + ERROR = "ERROR" diff --git a/sinch/domains/verification/exceptions.py b/sinch/domains/verification/exceptions.py new file mode 100644 index 0000000..91d913a --- /dev/null +++ b/sinch/domains/verification/exceptions.py @@ -0,0 +1,5 @@ +from sinch.core.exceptions import SinchException + + +class VerificationException(SinchException): + pass diff --git a/sinch/domains/verification/models/__init__.py b/sinch/domains/verification/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sinch/domains/verification/models/requests.py b/sinch/domains/verification/models/requests.py new file mode 100644 index 0000000..fddb010 --- /dev/null +++ b/sinch/domains/verification/models/requests.py @@ -0,0 +1,40 @@ +from dataclasses import dataclass +from sinch.core.models.base_model import SinchRequestBaseModel +from sinch.domains.verification.enums import VerificationMethod + + +@dataclass +class StartVerificationRequest(SinchRequestBaseModel): + identity: dict + method: VerificationMethod + reference: str + custom: str + flash_call_options: dict + + +@dataclass +class ReportVerificationByIdentityRequest(SinchRequestBaseModel): + endpoint: str + verification_report_request: dict + + +@dataclass +class ReportVerificationByIdRequest(SinchRequestBaseModel): + id: str + verification_report_request: dict + + +@dataclass +class GetVerificationStatusByReferenceRequest(SinchRequestBaseModel): + reference: str + + +@dataclass +class GetVerificationStatusByIdRequest(SinchRequestBaseModel): + id: str + + +@dataclass +class GetVerificationStatusByIdentityRequest(SinchRequestBaseModel): + endpoint: str + method: VerificationMethod diff --git a/sinch/domains/verification/models/responses.py b/sinch/domains/verification/models/responses.py new file mode 100644 index 0000000..911c119 --- /dev/null +++ b/sinch/domains/verification/models/responses.py @@ -0,0 +1,74 @@ +from dataclasses import dataclass +from sinch.core.models.base_model import SinchBaseModel +from sinch.domains.verification.enums import VerificationMethod, VerificationStatus + + +@dataclass +class StartVerificationResponse(SinchBaseModel): + id: str + method: VerificationMethod + _links: list + + +@dataclass +class StartSMSInitiateVerificationResponse(StartVerificationResponse): + sms: dict + + +@dataclass +class StartFlashCallInitiateVerificationResponse(StartVerificationResponse): + flashcall: dict + + +@dataclass +class StartCalloutInitiateVerificationResponse(StartVerificationResponse): + callout: dict + + +@dataclass +class StartDataInitiateVerificationResponse(StartVerificationResponse): + seamless: dict + + +@dataclass +class VerificationResponse(SinchBaseModel): + id: str + method: VerificationMethod + status: VerificationStatus + price: dict + identity: dict + country_id: str + verification_timestamp: str + reference: str + reason: str + call_complete: bool + + +@dataclass +class ReportVerificationResponse(VerificationResponse): + pass + + +@dataclass +class ReportVerificationByIdentityResponse(ReportVerificationResponse): + pass + + +@dataclass +class ReportVerificationByIdResponse(ReportVerificationResponse): + pass + + +@dataclass +class GetVerificationStatusByReferenceResponse(VerificationResponse): + pass + + +@dataclass +class GetVerificationStatusByIdResponse(VerificationResponse): + pass + + +@dataclass +class GetVerificationStatusByIdentityResponse(VerificationResponse): + pass diff --git a/tests/conftest.py b/tests/conftest.py index 4bf19fa..a7cb74f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -42,6 +42,7 @@ def configure_origin( templates_origin, auth_origin, sms_origin, + verification_origin, disable_ssl ): if auth_origin: @@ -59,6 +60,9 @@ def configure_origin( if sms_origin: sinch_client.configuration.sms_origin = sms_origin + if verification_origin: + sinch_client.configuration.verification_origin = verification_origin + if disable_ssl: sinch_client.configuration.disable_https = True @@ -100,6 +104,11 @@ def sms_origin(): return os.getenv("SMS_ORIGIN") +@pytest.fixture +def verification_origin(): + return os.getenv("VERIFICATION_ORIGIN") + + @pytest.fixture def templates_origin(): return os.getenv("TEMPLATES_ORIGIN") @@ -120,6 +129,21 @@ def origin_phone_number(): return os.getenv("ORIGIN_PHONE_NUMBER") +@pytest.fixture +def application_key(): + return os.getenv("APPLICATION_KEY") + + +@pytest.fixture +def application_secret(): + return os.getenv("APPLICATION_SECRET") + + +@pytest.fixture +def verification_id(): + return os.getenv("VERIFICATION_ID") + + @pytest.fixture def app_id(): return os.getenv("APP_ID") @@ -135,6 +159,21 @@ def empty_project_id(): return os.getenv("EMPTY_PROJECT_ID") +@pytest.fixture +def verification_request_signature(): + return os.getenv("VERIFICATION_REQUEST_SIGNATURE") + + +@pytest.fixture +def verification_request_with_empty_body_signature(): + return os.getenv("VERIFICATION_REQUEST_WITH_EMPTY_BODY_SIGNATURE") + + +@pytest.fixture +def verification_request_signature_timestamp(): + return os.getenv("VERIFICATION_REQUEST_SIGNATURE_TIMESTAMP") + + @pytest.fixture def http_response(): return HTTPResponse( @@ -155,7 +194,7 @@ def sms_http_response(): return HTTPResponse( status_code=404, body={ - "text": "Nobody expects the Spanish Inquisition!" + "text": "Nobody expects the Spanish Inquisition!" }, headers={ "SAMPLE_HEADER": "test" @@ -237,34 +276,6 @@ def second_int_based_pagination_response(): ) -@pytest.fixture -def int_based_pagination_request_data(): - return IntBasedPaginationRequest( - page=0, - page_size=2 - ) - - -@pytest.fixture -def first_int_based_pagination_response(): - return IntBasedPaginationResponse( - count=4, - page=0, - page_size=2, - pig_dogs=["Bartosz", "Piotr"] - ) - - -@pytest.fixture -def second_int_based_pagination_response(): - return IntBasedPaginationResponse( - count=4, - page=1, - page_size=2, - pig_dogs=["Walaszek", "Połać"] - ) - - @pytest.fixture def third_int_based_pagination_response(): return IntBasedPaginationResponse( @@ -275,15 +286,26 @@ def third_int_based_pagination_response(): ) +@pytest.fixture +def int_based_pagination_request_data(): + return IntBasedPaginationRequest( + page=0, + page_size=2 + ) + + @pytest.fixture def sinch_client_sync( key_id, key_secret, + application_key, + application_secret, numbers_origin, conversation_origin, templates_origin, auth_origin, sms_origin, + verification_origin, disable_ssl, project_id ): @@ -291,13 +313,16 @@ def sinch_client_sync( Client( key_id=key_id, key_secret=key_secret, - project_id=project_id + project_id=project_id, + application_key=application_key, + application_secret=application_secret ), numbers_origin, conversation_origin, templates_origin, auth_origin, sms_origin, + verification_origin, disable_ssl ) @@ -306,11 +331,14 @@ def sinch_client_sync( def sinch_client_async( key_id, key_secret, + application_key, + application_secret, numbers_origin, conversation_origin, templates_origin, auth_origin, sms_origin, + verification_origin, disable_ssl, project_id ): @@ -318,12 +346,15 @@ def sinch_client_async( ClientAsync( key_id=key_id, key_secret=key_secret, - project_id=project_id + project_id=project_id, + application_key=application_key, + application_secret=application_secret ), numbers_origin, conversation_origin, templates_origin, auth_origin, sms_origin, + verification_origin, disable_ssl ) diff --git a/tests/e2e/verification/test_get_report_using_id.py b/tests/e2e/verification/test_get_report_using_id.py new file mode 100644 index 0000000..6896290 --- /dev/null +++ b/tests/e2e/verification/test_get_report_using_id.py @@ -0,0 +1,23 @@ +from sinch.domains.verification.models.responses import GetVerificationStatusByIdResponse + + +def test_get_report_verification_using_id( + sinch_client_sync, + phone_number, + verification_id +): + verification_response = sinch_client_sync.verification.verification_status.get_by_id( + id=verification_id + ) + assert isinstance(verification_response, GetVerificationStatusByIdResponse) + + +async def test_get_report_verification_using_id_async( + sinch_client_async, + phone_number, + verification_id +): + verification_response = await sinch_client_async.verification.verification_status.get_by_id( + id=verification_id + ) + assert isinstance(verification_response, GetVerificationStatusByIdResponse) diff --git a/tests/e2e/verification/test_get_report_using_identity.py b/tests/e2e/verification/test_get_report_using_identity.py new file mode 100644 index 0000000..569369f --- /dev/null +++ b/tests/e2e/verification/test_get_report_using_identity.py @@ -0,0 +1,23 @@ +from sinch.domains.verification.models.responses import GetVerificationStatusByIdentityResponse + + +def test_get_report_verification_using_identity( + sinch_client_sync, + phone_number +): + verification_response = sinch_client_sync.verification.verification_status.get_by_identity( + endpoint=phone_number, + method="sms" + ) + assert isinstance(verification_response, GetVerificationStatusByIdentityResponse) + + +async def test_get_report_verification_using_identity_async( + sinch_client_async, + phone_number +): + verification_response = await sinch_client_async.verification.verification_status.get_by_identity( + endpoint=phone_number, + method="sms" + ) + assert isinstance(verification_response, GetVerificationStatusByIdentityResponse) diff --git a/tests/e2e/verification/test_get_report_using_reference.py b/tests/e2e/verification/test_get_report_using_reference.py new file mode 100644 index 0000000..b2443f1 --- /dev/null +++ b/tests/e2e/verification/test_get_report_using_reference.py @@ -0,0 +1,19 @@ +from sinch.domains.verification.models.responses import GetVerificationStatusByReferenceResponse + + +def test_get_report_verification_using_reference( + sinch_client_sync +): + verification_response = sinch_client_sync.verification.verification_status.get_by_reference( + reference="random" + ) + assert isinstance(verification_response, GetVerificationStatusByReferenceResponse) + + +async def test_get_report_verification_using_reference_async( + sinch_client_async +): + verification_response = await sinch_client_async.verification.verification_status.get_by_reference( + reference="random" + ) + assert isinstance(verification_response, GetVerificationStatusByReferenceResponse) diff --git a/tests/e2e/verification/test_report_verification_using_id.py b/tests/e2e/verification/test_report_verification_using_id.py new file mode 100644 index 0000000..67381db --- /dev/null +++ b/tests/e2e/verification/test_report_verification_using_id.py @@ -0,0 +1,35 @@ +from sinch.domains.verification.models.responses import ReportVerificationByIdResponse + + +def test_report_verification_using_id_and_sms( + sinch_client_sync, + phone_number, + verification_id +): + verification_response = sinch_client_sync.verification.verifications.report_by_id( + id=verification_id, + verification_report_request={ + "method": "sms", + "sms": { + "code": "2302" + } + } + ) + assert isinstance(verification_response, ReportVerificationByIdResponse) + + +async def test_report_verification_using_id_and_sms_async( + sinch_client_async, + phone_number, + verification_id +): + verification_response = await sinch_client_async.verification.verifications.report_by_id( + id=verification_id, + verification_report_request={ + "method": "sms", + "sms": { + "code": "2302" + } + } + ) + assert isinstance(verification_response, ReportVerificationByIdResponse) diff --git a/tests/e2e/verification/test_report_verification_using_identity.py b/tests/e2e/verification/test_report_verification_using_identity.py new file mode 100644 index 0000000..816c6a6 --- /dev/null +++ b/tests/e2e/verification/test_report_verification_using_identity.py @@ -0,0 +1,33 @@ +from sinch.domains.verification.models.responses import ReportVerificationByIdentityResponse + + +def test_report_verification_using_identity_and_sms( + sinch_client_sync, + phone_number +): + verification_response = sinch_client_sync.verification.verifications.report_by_identity( + endpoint=phone_number, + verification_report_request={ + "method": "sms", + "sms": { + "code": "2302" + } + } + ) + assert isinstance(verification_response, ReportVerificationByIdentityResponse) + + +async def test_report_verification_using_identity_and_sms_async( + sinch_client_async, + phone_number +): + verification_response = await sinch_client_async.verification.verifications.report_by_identity( + endpoint=phone_number, + verification_report_request={ + "method": "sms", + "sms": { + "code": "2302" + } + } + ) + assert isinstance(verification_response, ReportVerificationByIdentityResponse) diff --git a/tests/e2e/verification/test_start_verification.py b/tests/e2e/verification/test_start_verification.py new file mode 100644 index 0000000..da3dfb2 --- /dev/null +++ b/tests/e2e/verification/test_start_verification.py @@ -0,0 +1,53 @@ +from sinch.domains.verification.models.responses import ( + StartSMSInitiateVerificationResponse, + StartFlashCallInitiateVerificationResponse +) +from sinch.domains.verification.enums import VerificationMethod + + +def test_start_verification_sms( + sinch_client_sync, + phone_number +): + verification_response = sinch_client_sync.verification.verifications.start( + method="sms", + identity={ + "type": "number", + "endpoint": phone_number + }, + reference="random" + ) + + assert isinstance(verification_response, StartSMSInitiateVerificationResponse) + + +def test_start_verification_flash_call( + sinch_client_sync, + phone_number +): + verification_response = sinch_client_sync.verification.verifications.start( + method=VerificationMethod.FLASHCALL.value, + identity={ + "type": "number", + "endpoint": phone_number + }, + reference="random5" + ) + + assert isinstance(verification_response, StartFlashCallInitiateVerificationResponse) + + +async def test_start_verification_async( + sinch_client_async, + phone_number +): + verification_response = await sinch_client_async.verification.verifications.start( + method="sms", + identity={ + "type": "number", + "endpoint": phone_number + }, + reference="random" + ) + + assert isinstance(verification_response, StartSMSInitiateVerificationResponse) diff --git a/tests/integration/test_logging.py b/tests/integration/test_logging.py index c551416..0e62482 100644 --- a/tests/integration/test_logging.py +++ b/tests/integration/test_logging.py @@ -17,18 +17,21 @@ def test_default_logger(sinch_client_sync, caplog): sinch_client = mock_http_transport(sinch_client_sync) http_endpoint = Mock() sinch_client.configuration.transport.request(http_endpoint) - assert len(caplog.records) == 2 + assert len(caplog.get_records("call")) == 2 assert caplog.records[0].levelname == "DEBUG" def test_changing_logger_name_within_the_client(sinch_client_sync, caplog): + logger_name = "SumOlimpijczyk" sinch_client_sync.configuration.logger.setLevel(logging.DEBUG) - sinch_client_sync.configuration.logger.name = "SumOlimpijczyk" + sinch_client_sync.configuration.logger.name = logger_name sinch_client = mock_http_transport(sinch_client_sync) + caplog.set_level(logging.DEBUG, logger=logger_name) http_endpoint = Mock() sinch_client.configuration.transport.request(http_endpoint) - assert len(caplog.records) == 2 - assert caplog.records[0].name == "SumOlimpijczyk" + + assert len([record for record in caplog.records if record.name == logger_name]) == 2 + assert caplog.records[0].name == logger_name def test_logger_with_logging_to_file(sinch_client_sync): diff --git a/tests/integration/test_request_signing.py b/tests/integration/test_request_signing.py new file mode 100644 index 0000000..46e49ab --- /dev/null +++ b/tests/integration/test_request_signing.py @@ -0,0 +1,60 @@ +import json +from sinch.core.signature import Signature + + +def test_request_signature( + sinch_client_sync, + verification_request_signature, + verification_request_signature_timestamp +): + signature = Signature( + sinch_client_sync, + http_method="GET", + request_data=json.dumps({"test": "test"}), + request_uri="/verification/v1/verifications", + signature_timestamp=verification_request_signature_timestamp + ) + signature.calculate() + + assert signature.authorization_signature + assert isinstance(signature.authorization_signature, str) + assert verification_request_signature == signature.authorization_signature + + +def test_request_signature_using_empty_body( + sinch_client_sync, + verification_request_with_empty_body_signature, + verification_request_signature_timestamp +): + signature = Signature( + sinch_client_sync, + http_method="POST", + request_data=None, + request_uri="/verification/v1/verifications", + signature_timestamp=verification_request_signature_timestamp + ) + signature.calculate() + + assert signature.authorization_signature + assert isinstance(signature.authorization_signature, str) + assert verification_request_with_empty_body_signature == signature.authorization_signature + + +def test_get_headers_with_signature_and_async_client( + sinch_client_async, + verification_request_with_empty_body_signature, + verification_request_signature_timestamp +): + signature = Signature( + sinch_client_async, + http_method="POST", + request_data=None, + request_uri="/verification/v1/verifications", + signature_timestamp=verification_request_signature_timestamp + ) + headers = signature.get_http_headers_with_signature() + + assert "x-timestamp" in headers + assert "Authorization" in headers + assert "Content-Type" in headers + assert verification_request_with_empty_body_signature in headers["Authorization"] diff --git a/tests/integration/test_token_refresh.py b/tests/integration/test_token_refresh.py index 51c5598..36b97fd 100644 --- a/tests/integration/test_token_refresh.py +++ b/tests/integration/test_token_refresh.py @@ -3,10 +3,15 @@ from sinch.core.models.http_response import HTTPResponse from sinch.domains.authentication.endpoints.oauth import OAuthEndpoint +from sinch.domains.numbers.endpoints.available.list_available_numbers import AvailableNumbersEndpoint from sinch.domains.authentication.exceptions import AuthenticationException -def test_handling_401_without_expiration(sinch_client_sync, auth_token_as_dict): +def test_handling_401_without_expiration( + sinch_client_sync, + auth_token_as_dict, + project_id +): http_response = HTTPResponse( status_code=401, headers={ @@ -26,7 +31,8 @@ def test_handling_401_without_expiration(sinch_client_sync, auth_token_as_dict): def test_handling_401_with_expired_token_gets_invalidated_and_refreshed( sinch_client_sync, auth_token_as_dict, - expired_token_http_response + expired_token_http_response, + project_id ): sinch_client_sync.configuration.token_manager.set_auth_token(auth_token_as_dict) sinch_client_sync.configuration.transport.request = Mock() @@ -34,7 +40,7 @@ def test_handling_401_with_expired_token_gets_invalidated_and_refreshed( transport_response = sinch_client_sync.configuration.transport.handle_response( http_response=expired_token_http_response, - endpoint=OAuthEndpoint() + endpoint=AvailableNumbersEndpoint(project_id, {}) ) assert transport_response == "Token Refresh!" @@ -42,7 +48,8 @@ def test_handling_401_with_expired_token_gets_invalidated_and_refreshed( async def test_handling_401_with_expired_token_gets_refreshed_async( sinch_client_async, auth_token_as_dict, - expired_token_http_response + expired_token_http_response, + project_id ): sinch_client_async.configuration.token_manager.set_auth_token(auth_token_as_dict) sinch_client_async.configuration.transport.request = AsyncMock() @@ -50,6 +57,6 @@ async def test_handling_401_with_expired_token_gets_refreshed_async( transport_response = await sinch_client_async.configuration.transport.handle_response( http_response=expired_token_http_response, - endpoint=OAuthEndpoint() + endpoint=AvailableNumbersEndpoint(project_id, {}) ) assert transport_response == "Token Refresh!"