diff --git a/CHANGES.md b/CHANGES.md index 0a9e8d51..567cc906 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,92 +1,106 @@ +# 2.5.x + +- Support for Independent SMS, Voice and Verify APIs with tests as well as current client methods +- Getters/Setters to extract/rewrite custom attributes +- PSD2 Verification support +- Dropping support for Python 2.7 +- Roadmap to better error handling +- Supporting Python 3.8 + # 2.4.0 -* Application V2 API added under `Client.application_v2` -* Existing application methods under `Client` are now deprecated. + +- Application V2 API added under `Client.application_v2` +- Existing application methods under `Client` are now deprecated. # 2.3.0 -* Explicit parameter list for the `nexmo.Client` constructor. **This may cause errors in code passing incorrect or spurious arguments to the Client constructor.** -* Secret Management -* Support for Authorization header authentication. + +- Explicit parameter list for the `nexmo.Client` constructor. **This may cause errors in code passing incorrect or spurious arguments to the Client constructor.** +- Secret Management +- Support for Authorization header authentication. # 2.2.0 -* Add support for `redact_transaction`. + +- Add support for `redact_transaction`. # 2.1.0 -* Add support for `get_recording` -* Add support for SMS conversion -* Add debug logging for most calls, under the 'nexmo' logger. -* Internal refactoring (affects only private methods.) + +- Add support for `get_recording` +- Add support for SMS conversion +- Add debug logging for most calls, under the 'nexmo' logger. +- Internal refactoring (affects only private methods.) # 2.0.0 -* Drop support for Python 3.3 (in line with the cryptography library we depend upon) -* Ensure timestamp is added the params list if signing requests -* Avoid value injection in signature auth. -* Add support for different hashes for signature generation (thanks @trancee!) -* Tests ported to pytest + +- Drop support for Python 3.3 (in line with the cryptography library we depend upon) +- Ensure timestamp is added the params list if signing requests +- Avoid value injection in signature auth. +- Add support for different hashes for signature generation (thanks @trancee!) +- Tests ported to pytest # 1.5.0 -* Add ability to provide a file path as private_key param no the nexmo.Client constructor +- Add ability to provide a file path as private_key param no the nexmo.Client constructor -* Add send/stop endpoints for audio/speech/dtmf +- Add send/stop endpoints for audio/speech/dtmf -* Add new number insight endpoints +- Add new number insight endpoints # 1.4.0 -* Add new Voice API call methods +- Add new Voice API call methods -* Add Application API methods +- Add Application API methods -* Add check_signature method for checking callback signatures +- Add check_signature method for checking callback signatures -* Deprecate old Verify API methods +- Deprecate old Verify API methods # 1.3.0 -* Add get_sms_pricing method +- Add get_sms_pricing method -* Add get_voice_pricing method +- Add get_voice_pricing method -* Add get_event_alert_numbers method to get opt-in/opt-out numbers +- Add get_event_alert_numbers method to get opt-in/opt-out numbers -* Add resubscribe_event_alert_number method to opt-in a number +- Add resubscribe_event_alert_number method to opt-in a number -* Add more clearly named methods for Verify API +- Add more clearly named methods for Verify API -* Add app_name and app_version options +- Add app_name and app_version options # 1.2.0 -* Add topup method +- Add topup method -* Add update_settings method +- Add update_settings method -* Add api_host attribute +- Add api_host attribute -* Add ClientError and ServerError classes +- Add ClientError and ServerError classes # 1.1.0 -* Move repository to https://github.com/Nexmo/nexmo-python +- Move repository to https://github.com/Nexmo/nexmo-python -* Add get_basic_number_insight method for Number Insight Basic API +- Add get_basic_number_insight method for Number Insight Basic API -* Add get_number_insight method for Number Insight Standard API +- Add get_number_insight method for Number Insight Standard API -* Add User-Agent header to requests +- Add User-Agent header to requests # 1.0.3 -* Change license from LGPL-3.0 to MIT +- Change license from LGPL-3.0 to MIT # 1.0.2 -* Remove merge helper function +- Remove merge helper function # 1.0.1 -* Python 3.x fixes +- Python 3.x fixes # 1.0.0 -* First version! +- First version! diff --git a/README.md b/README.md index 1d03e7bf..98a27e4d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ This is the Python client library for Nexmo's API. To use it you'll need a Nexmo account. Sign up [for free at nexmo.com][signup]. - * [Installation](#installation) * [Usage](#usage) * [SMS API](#sms-api) @@ -23,7 +22,6 @@ need a Nexmo account. Sign up [for free at nexmo.com][signup]. * [Frequently Asked Questions](#frequently-asked-questions) * [License](#license) - ## Installation To install the Python client library using pip: @@ -86,7 +84,7 @@ from nexmo import Sms from nexmo.sms import Sms #Option 3 -import nexmo #then tou can use nexmo.Sms() to create an instance +import nexmo #then you can use nexmo.Sms() to create an instance ``` - Create an instance @@ -141,7 +139,6 @@ sms.submit_sms_conversion(response['message-id']) ### Make a call - ```python from nexmo import Client, Voice client = Client(application_id=APPLICATION_ID, private_key=PRIVATE_KEY) @@ -162,10 +159,8 @@ voice = Voice(client) voice.get_calls() ``` - ### Retrieve a single call - ```python from nexmo import Client, Voice client = Client(application_id=APPLICATION_ID, private_key=PRIVATE_KEY) @@ -173,7 +168,6 @@ voice = Voice(client) voice.get_call(uuid) ``` - ### Update a call ```python @@ -188,7 +182,6 @@ response = voice.create_all({ voice.update_call(response['uuid'], action='hangup') ``` - ### Stream audio to a call ```python @@ -204,7 +197,6 @@ response = voice.create_call({ voice.send_audio(response['uuid'],stream_url=[stream_url]) ``` - ### Stop streaming audio to a call ```python @@ -221,7 +213,6 @@ voice.send_audio(response['uuid'],stream_url=[stream_url]) voice.stop_audio(response['uuid']) ``` - ### Send a synthesized speech message to a call ```python @@ -253,7 +244,6 @@ voice.send_speech(response['uuid'], text='Hello from nexmo') ### Send DTMF tones to a call - ```python from nexmo import Client, Voice client = Client(application_id=APPLICATION_ID, private_key=PRIVATE_KEY) @@ -272,26 +262,30 @@ voice.send_dtmf(response['uuid'], digits='1234') response = client.get_recording(RECORDING_URL) ``` -# Verify Class +## Verify API -## Creating an instance of the class +### Verify Class -To create an instance of the Verify class, Just follow these steps: +#### Creating an instance of the class + +To create an instance of the Verify class, Just follow the next steps: +​ - **Import the class from module** (3 different ways) ```python #First way from nexmo import Verify - +​ #Second way from nexmo.verify import Verify - +​ #Third valid way -import nexmo #then use nexmo.Verify() to create an instance +import nexmo #then you can use nexmo.Verify() to create an instance ``` - **Create the instance** + ​ ```python #First way - pass key and secret to the constructor @@ -412,7 +406,6 @@ else: print("Error: %s" % response["error_text"]) ``` - ## Number Insight API ### Basic Number Insight @@ -559,7 +552,6 @@ client.host('mio.nexmo.com') #Change host to mio.nexmo.com - this change will be These attributes are private in the client class and the only way to access them is using the getters/setters we provide. - ```python from nexmo import Client @@ -571,7 +563,9 @@ client.api_host('myapi.nexmo.com') # rewrite the value of api_host ``` ## Frequently Asked Questions + ### Dropping support for Python 2.7 + Back in 2014 when Guido van Rossum, Python's creator and principal author, made the announcement, January 1, 2020 seemed pretty far away. Python 2.7’s sunset has happened, after which there’ll be absolutely no more support from the core Python team. Many utilized projects pledge to drop Python 2 support in or before 2020. [(Official statement here)](https://www.python.org/doc/sunset-python-2/). Just because 2.7 isn’t going to be maintained past 2020 doesn’t mean your applications or libraries suddenly stop working but as of this moment we won't give official support for upcoming releases. Please read the official ["Porting Python 2 Code to Python 3" guide](https://docs.python.org/3/howto/pyporting.html). Please also read the [Python 3 Statement Practicalities](https://python3statement.org/practicalities/) for advice on sunsetting your Python 2 code. @@ -580,25 +574,25 @@ Just because 2.7 isn’t going to be maintained past 2020 doesn’t mean your ap The following is a list of Vonage APIs and whether the Python SDK provides support for them: -| API | API Release Status | Supported? -|----------|:---------:|:-------------:| -| Account API | General Availability |✅| -| Alerts API | General Availability |✅| -| Application API | General Availability |✅| -| Audit API | Beta |❌| -| Conversation API | Beta |❌| -| Dispatch API | Beta |❌| -| External Accounts API | Beta |❌| -| Media API | Beta | ❌| -| Messages API | Beta |❌| -| Number Insight API | General Availability |✅| -| Number Management API | General Availability |✅| -| Pricing API | General Availability |✅| -| Redact API | General Availability |✅| -| Reports API | Beta |❌| -| SMS API | General Availability |✅| -| Verify API | General Availability |✅| -| Voice API | General Availability |✅| +| API | API Release Status | Supported? | +| --------------------- | :------------------: | :--------: | +| Account API | General Availability | ✅ | +| Alerts API | General Availability | ✅ | +| Application API | General Availability | ✅ | +| Audit API | Beta | ❌ | +| Conversation API | Beta | ❌ | +| Dispatch API | Beta | ❌ | +| External Accounts API | Beta | ❌ | +| Media API | Beta | ❌ | +| Messages API | Beta | ❌ | +| Number Insight API | General Availability | ✅ | +| Number Management API | General Availability | ✅ | +| Pricing API | General Availability | ✅ | +| Redact API | General Availability | ✅ | +| Reports API | Beta | ❌ | +| SMS API | General Availability | ✅ | +| Verify API | General Availability | ✅ | +| Voice API | General Availability | ✅ | ## Contributing diff --git a/requirements.txt b/requirements.txt index 91203619..cf6661f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ pytest==3.1.2 pytest-cov==2.5.1 responses==0.5.1 coveralls -glom==18.3.1 +glom==18.3.1 \ No newline at end of file diff --git a/setup.py b/setup.py index f3b7f6f4..6a7a4da9 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name="nexmo", - version="2.5.0", + version="2.5.2", description="Nexmo Client Library for Python", long_description=long_description, long_description_content_type="text/markdown", @@ -22,7 +22,12 @@ packages=find_packages(where="src"), package_dir={"": "src"}, platforms=["any"], - install_requires=["requests>=2.4.2", "PyJWT[crypto]>=1.6.4", "pytz>=2018.5"], + install_requires=[ + "requests>=2.4.2", + "PyJWT[crypto]>=1.6.4", + "pytz>=2018.5", + "Deprecated", + ], python_requires=">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", tests_require=["cryptography>=2.3.1"], classifiers=[ diff --git a/src/nexmo/__init__.py b/src/nexmo/__init__.py index 1eb0cd92..68b68e40 100644 --- a/src/nexmo/__init__.py +++ b/src/nexmo/__init__.py @@ -19,6 +19,7 @@ from uuid import uuid4 import warnings import re +from deprecated import deprecated string_types = (str, bytes) @@ -127,9 +128,6 @@ def __init__( self.application_v2 = ApplicationV2(api_server) self.session = requests.Session() - - # Internal Verify Object - a method that return a verify instance, just for cool definitions - self.Verify = Verify(self) # Get and Set __host attribute def host(self, value=None): @@ -152,6 +150,21 @@ def api_host(self, value=None): def auth(self, params=None, **kwargs): self.auth_params = params or kwargs + @deprecated(reason="nexmo.Client#send_message is deprecated. Use Sms#send_message instead") + def send_message(self, params): + """ + Send an SMS message. + Requires a client initialized with `key` and either `secret` or `signature_secret`. + :: + client.send_message({ + "to": MY_CELLPHONE, + "from": MY_NEXMO_NUMBER, + "text": "Hello From Nexmo!", + }) + :param dict params: A dict of values described at `Send an SMS `_ + """ + return self.post(self.host(), "/sms/json", params, supports_signature_auth=True) + def get_balance(self): return self.get(self.host(), "/account/get-balance") @@ -216,6 +229,24 @@ def send_ussd_prompt_message(self, params=None, **kwargs): def send_2fa_message(self, params=None, **kwargs): return self.post(self.host(), "/sc/us/2fa/json", params or kwargs) + def submit_sms_conversion(self, message_id, delivered=True, timestamp=None): + """ + Notify Nexmo that an SMS was successfully received. + + :param message_id: The `message-id` str returned by the send_message call. + :param delivered: A `bool` indicating that the message was or was not successfully delivered. + :param timestamp: A `datetime` object containing the time the SMS arrived. + :return: The parsed response from the server. On success, the bytestring b'OK' + """ + params = { + "message-id": message_id, + "delivered": delivered, + "timestamp": timestamp or datetime.now(pytz.utc), + } + # Ensure timestamp is a string: + _format_date_param(params, "timestamp") + return self.post(self.api_host(), "/conversions/sms", params) + def send_event_alert_message(self, params=None, **kwargs): return self.post(self.host(), "/sc/us/alert/json", params or kwargs) @@ -228,6 +259,91 @@ def get_event_alert_numbers(self): def resubscribe_event_alert_number(self, params=None, **kwargs): return self.post(self.host(), "/sc/us/alert/opt-in/manage/json", params or kwargs) + def initiate_call(self, params=None, **kwargs): + return self.post(self.host(), "/call/json", params or kwargs) + + def initiate_tts_call(self, params=None, **kwargs): + return self.post(self.api_host(), "/tts/json", params or kwargs) + + def initiate_tts_prompt_call(self, params=None, **kwargs): + return self.post(self.api_host(), "/tts-prompt/json", params or kwargs) + + @deprecated(reason="nexmo.Client#start_verification is deprecated. Use Verify#start_verification instead") + def start_verification(self, params=None, **kwargs): + return self.post(self.api_host(), "/verify/json", params or kwargs) + + def send_verification_request(self, params=None, **kwargs): + warnings.warn( + "nexmo.Client#send_verification_request is deprecated (use Verify#start_verification instead)", + DeprecationWarning, + stacklevel=2, + ) + + return self.post(self.api_host(), "/verify/json", params or kwargs) + + @deprecated(reason="nexmo.Client#check_verification is deprecated. Use Verify#check instead") + def check_verification(self, request_id, params=None, **kwargs): + return self.post( + self.api_host(), + "/verify/check/json", + dict(params or kwargs, request_id=request_id), + ) + + def check_verification_request(self, params=None, **kwargs): + warnings.warn( + "nexmo.Client#check_verification_request is deprecated (use Verify#check instead)", + DeprecationWarning, + stacklevel=2, + ) + + return self.post(self.api_host(), "/verify/check/json", params or kwargs) + + @deprecated(reason="nexmo.Client#start_psd2_verification_request is deprecated. Use Verify#psd2 instead") + def start_psd2_verification_request(self, params=None, **kwargs): + return self.post(self.api_host(), "/verify/psd2/json", params or kwargs) + + @deprecated(reason="nexmo.Client#get_verification is deprecated. Use Verify#search instead") + def get_verification(self, request_id): + return self.get( + self.api_host(), "/verify/search/json", {"request_id": request_id} + ) + + def get_verification_request(self, request_id): + warnings.warn( + "nexmo.Client#get_verification_request is deprecated (use Verify#search instead)", + DeprecationWarning, + stacklevel=2, + ) + + return self.get( + self.api_host(), "/verify/search/json", {"request_id": request_id} + ) + + @deprecated(reason="nexmo.Client#cancel_verification is deprecated. Use Verify#cancel instead") + def cancel_verification(self, request_id): + return self.post( + self.api_host(), + "/verify/control/json", + {"request_id": request_id, "cmd": "cancel"}, + ) + + @deprecated(reason="nexmo.Client#trigger_next_verification_event is deprecated. Use Verify#trigger_next_event instead") + def trigger_next_verification_event(self, request_id): + return self.post( + self.api_host(), + "/verify/control/json", + {"request_id": request_id, "cmd": "trigger_next_event"}, + ) + + def control_verification_request(self, params=None, **kwargs): + warnings.warn( + "nexmo.Client#control_verification_request is deprecated", + DeprecationWarning, + stacklevel=2, + ) + + return self.post(self.api_host(), "/verify/control/json", params or kwargs) + def get_basic_number_insight(self, params=None, **kwargs): return self.get(self.api_host(), "/ni/basic/json", params or kwargs) @@ -306,6 +422,50 @@ def delete_application(self, application_id): "/v1/applications/{application_id}".format(application_id=application_id), ) + @deprecated(reason="nexmo.Client#create_call is deprecated. Use Voice#create_call instead") + def create_call(self, params=None, **kwargs): + return self._jwt_signed_post("/v1/calls", params or kwargs) + + @deprecated(reason="nexmo.Client#get_calls is deprecated. Use Voice#get_calls instead") + def get_calls(self, params=None, **kwargs): + return self._jwt_signed_get("/v1/calls", params or kwargs) + + @deprecated(reason="nexmo.Client#get_call is deprecated. Use Voice#get_call instead") + def get_call(self, uuid): + return self._jwt_signed_get("/v1/calls/{uuid}".format(uuid=uuid)) + + @deprecated(reason="nexmo.Client#update_call is deprecated. Use Voice#update_call instead") + def update_call(self, uuid, params=None, **kwargs): + return self._jwt_signed_put( + "/v1/calls/{uuid}".format(uuid=uuid), params or kwargs + ) + + @deprecated(reason="nexmo.Client#send_audio is deprecated. Use Voice#send_audio instead") + def send_audio(self, uuid, params=None, **kwargs): + return self._jwt_signed_put( + "/v1/calls/{uuid}/stream".format(uuid=uuid), params or kwargs + ) + + @deprecated(reason="nexmo.Client#stop_audio is deprecated. Use Voice#stop_audio instead") + def stop_audio(self, uuid): + return self._jwt_signed_delete("/v1/calls/{uuid}/stream".format(uuid=uuid)) + + @deprecated(reason="nexmo.Client#send_speech is deprecated. Use Voice#send_speech instead") + def send_speech(self, uuid, params=None, **kwargs): + return self._jwt_signed_put( + "/v1/calls/{uuid}/talk".format(uuid=uuid), params or kwargs + ) + + @deprecated(reason="nexmo.Client#stop_speech is deprecated. Use Voice#stop_speech instead") + def stop_speech(self, uuid): + return self._jwt_signed_delete("/v1/calls/{uuid}/talk".format(uuid=uuid)) + + @deprecated(reason="nexmo.Client#send_dtmf is deprecated. Use Voice#send_dtmf instead") + def send_dtmf(self, uuid, params=None, **kwargs): + return self._jwt_signed_put( + "/v1/calls/{uuid}/dtmf".format(uuid=uuid), params or kwargs + ) + def get_recording(self, url): hostname = urlparse(url).hostname return self.parse(hostname, self.session.get(url, headers=self._headers())) @@ -540,6 +700,43 @@ def parse(self, host, response): ) raise ServerError(message) + def _jwt_signed_get(self, request_uri, params=None): + uri = "https://{api_host}{request_uri}".format( + api_host=self.api_host(), request_uri=request_uri + ) + + return self.parse( + self.api_host(), + self.session.get(uri, params=params or {}, headers=self._headers()), + ) + + def _jwt_signed_post(self, request_uri, params): + uri = "https://{api_host}{request_uri}".format( + api_host=self.api_host(), request_uri=request_uri + ) + + return self.parse( + self.api_host(), self.session.post(uri, json=params, headers=self._headers()) + ) + + def _jwt_signed_put(self, request_uri, params): + uri = "https://{api_host}{request_uri}".format( + api_host=self.api_host(), request_uri=request_uri + ) + + return self.parse( + self.api_host(), self.session.put(uri, json=params, headers=self._headers()) + ) + + def _jwt_signed_delete(self, request_uri): + uri = "https://{api_host}{request_uri}".format( + api_host=self.api_host(), request_uri=request_uri + ) + + return self.parse( + self.api_host(), self.session.delete(uri, headers=self._headers()) + ) + def _headers(self): token = self.generate_application_jwt() return dict(self.headers, Authorization=b"Bearer " + token) diff --git a/tests/conftest.py b/tests/conftest.py index a74d689e..fe39dcf9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -72,4 +72,4 @@ def verify(client, dummy_data): return nexmo.Verify( client - ) \ No newline at end of file + ) diff --git a/tests/test_sms.py b/tests/test_sms.py index fda960e8..005fcc56 100644 --- a/tests/test_sms.py +++ b/tests/test_sms.py @@ -50,3 +50,52 @@ def test_submit_sms_conversion(sms): sms.submit_sms_conversion("a-message-id") assert "message-id=a-message-id" in request_body() assert "timestamp" in request_body() + +@responses.activate +def test_deprecated_send_message(client, dummy_data): + stub(responses.POST, "https://rest.nexmo.com/sms/json") + + params = {"from": "Python", "to": "447525856424", "text": "Hey!"} + + assert isinstance(client.send_message(params), dict) + assert request_user_agent() == dummy_data.user_agent + assert "from=Python" in request_body() + assert "to=447525856424" in request_body() + assert "text=Hey%21" in request_body() + + +@responses.activate +def test_deprecated_authentication_error(client): + responses.add(responses.POST, "https://rest.nexmo.com/sms/json", status=401) + + with pytest.raises(nexmo.AuthenticationError): + client.send_message({}) + + +@responses.activate +def test_deprecated_client_error(client): + responses.add(responses.POST, "https://rest.nexmo.com/sms/json", status=400) + + with pytest.raises(nexmo.ClientError) as excinfo: + client.send_message({}) + excinfo.match(r"400 response from rest.nexmo.com") + + +@responses.activate +def test_deprecated_server_error(client): + responses.add(responses.POST, "https://rest.nexmo.com/sms/json", status=500) + + with pytest.raises(nexmo.ServerError) as excinfo: + client.send_message({}) + excinfo.match(r"500 response from rest.nexmo.com") + + +@responses.activate +def test_deprecated_submit_sms_conversion(client): + responses.add( + responses.POST, "https://api.nexmo.com/conversions/sms", status=200, body=b"OK" + ) + + client.submit_sms_conversion("a-message-id") + assert "message-id=a-message-id" in request_body() + assert "timestamp" in request_body() diff --git a/tests/test_verify.py b/tests/test_verify.py index 08e0aed5..8ec2ba54 100644 --- a/tests/test_verify.py +++ b/tests/test_verify.py @@ -1,6 +1,5 @@ from util import * - @responses.activate def test_start_verification(verify, dummy_data): stub(responses.POST, "https://api.nexmo.com/verify/json") @@ -64,4 +63,114 @@ def test_start_psd2_verification(verify, dummy_data): assert isinstance(verify.psd2(params), dict) assert request_user_agent() == dummy_data.user_agent assert "number=447525856424" in request_body() + assert "brand=MyApp" in request_body() + +@responses.activate +def test_deprecated_start_verification(client, dummy_data): + stub(responses.POST, "https://api.nexmo.com/verify/json") + + params = {"number": "447525856424", "brand": "MyApp"} + + assert isinstance(client.start_verification(params), dict) + assert request_user_agent() == dummy_data.user_agent + assert "number=447525856424" in request_body() + assert "brand=MyApp" in request_body() + + +@responses.activate +def test_deprecated_send_verification_request(client, dummy_data): + stub(responses.POST, "https://api.nexmo.com/verify/json") + + params = {"number": "447525856424", "brand": "MyApp"} + + assert isinstance(client.send_verification_request(params), dict) + assert request_user_agent() == dummy_data.user_agent + assert "number=447525856424" in request_body() + assert "brand=MyApp" in request_body() + + +@responses.activate +def test_deprecated_check_verification(client, dummy_data): + stub(responses.POST, "https://api.nexmo.com/verify/check/json") + + assert isinstance( + client.check_verification("8g88g88eg8g8gg9g90", code="123445"), dict + ) + assert request_user_agent() == dummy_data.user_agent + assert "code=123445" in request_body() + assert "request_id=8g88g88eg8g8gg9g90" in request_body() + + +@responses.activate +def test_deprecated_check_verification_request(client, dummy_data): + stub(responses.POST, "https://api.nexmo.com/verify/check/json") + + params = {"code": "123445", "request_id": "8g88g88eg8g8gg9g90"} + + assert isinstance(client.check_verification_request(params), dict) + assert request_user_agent() == dummy_data.user_agent + assert "code=123445" in request_body() + assert "request_id=8g88g88eg8g8gg9g90" in request_body() + + +@responses.activate +def test_deprecated_get_verification(client, dummy_data): + stub(responses.GET, "https://api.nexmo.com/verify/search/json") + + assert isinstance(client.get_verification("xxx"), dict) + assert request_user_agent() == dummy_data.user_agent + assert "request_id=xxx" in request_query() + + +@responses.activate +def test_deprecated_get_verification_request(client, dummy_data): + stub(responses.GET, "https://api.nexmo.com/verify/search/json") + + assert isinstance(client.get_verification_request("xxx"), dict) + assert request_user_agent() == dummy_data.user_agent + assert "request_id=xxx" in request_query() + + +@responses.activate +def test_deprecated_cancel_verification(client, dummy_data): + stub(responses.POST, "https://api.nexmo.com/verify/control/json") + + assert isinstance(client.cancel_verification("8g88g88eg8g8gg9g90"), dict) + assert request_user_agent() == dummy_data.user_agent + assert "cmd=cancel" in request_body() + assert "request_id=8g88g88eg8g8gg9g90" in request_body() + + +@responses.activate +def test_deprecated_trigger_next_verification_event(client, dummy_data): + stub(responses.POST, "https://api.nexmo.com/verify/control/json") + + assert isinstance( + client.trigger_next_verification_event("8g88g88eg8g8gg9g90"), dict + ) + assert request_user_agent() == dummy_data.user_agent + assert "cmd=trigger_next_event" in request_body() + assert "request_id=8g88g88eg8g8gg9g90" in request_body() + + +@responses.activate +def test_deprecated_control_verification_request(client, dummy_data): + stub(responses.POST, "https://api.nexmo.com/verify/control/json") + + params = {"cmd": "cancel", "request_id": "8g88g88eg8g8gg9g90"} + + assert isinstance(client.control_verification_request(params), dict) + assert request_user_agent() == dummy_data.user_agent + assert "cmd=cancel" in request_body() + assert "request_id=8g88g88eg8g8gg9g90" in request_body() + +@responses.activate +def test_deprecated_start_psd2_verification(client, dummy_data): + stub(responses.POST, "https://api.nexmo.com/verify/psd2/json") + + params = {"number": "447525856424", "brand": "MyApp"} + + assert isinstance(client.start_psd2_verification_request(params), dict) + assert request_user_agent() == dummy_data.user_agent + assert "number=447525856424" in request_body() assert "brand=MyApp" in request_body() \ No newline at end of file diff --git a/tests/test_voice.py b/tests/test_voice.py index bae67983..808484c8 100644 --- a/tests/test_voice.py +++ b/tests/test_voice.py @@ -151,3 +151,147 @@ def test_authorization_with_private_key_object(voice, dummy_data): request_authorization().split()[1], dummy_data.public_key, algorithm="RS256" ) assert token["application_id"] == dummy_data.application_id + + +@responses.activate +def test_deprecated_create_call(client, dummy_data): + stub(responses.POST, "https://api.nexmo.com/v1/calls") + + params = { + "to": [{"type": "phone", "number": "14843331234"}], + "from": {"type": "phone", "number": "14843335555"}, + "answer_url": ["https://example.com/answer"], + } + + assert isinstance(client.create_call(params), dict) + assert request_user_agent() == dummy_data.user_agent + assert request_content_type() == "application/json" + + +@responses.activate +def test_deprecated_get_calls(client, dummy_data): + stub(responses.GET, "https://api.nexmo.com/v1/calls") + + assert isinstance(client.get_calls(), dict) + assert request_user_agent() == dummy_data.user_agent + assert_re(r"\ABearer ", request_authorization()) + + +@responses.activate +def test_deprecated_get_call(client, dummy_data): + stub(responses.GET, "https://api.nexmo.com/v1/calls/xx-xx-xx-xx") + + assert isinstance(client.get_call("xx-xx-xx-xx"), dict) + assert request_user_agent() == dummy_data.user_agent + assert_re(r"\ABearer ", request_authorization()) + + +@responses.activate +def test_deprecated_update_call(client, dummy_data): + stub(responses.PUT, "https://api.nexmo.com/v1/calls/xx-xx-xx-xx") + + assert isinstance(client.update_call("xx-xx-xx-xx", action="hangup"), dict) + assert request_user_agent() == dummy_data.user_agent + assert request_content_type() == "application/json" + assert request_body() == b'{"action": "hangup"}' + + +@responses.activate +def test_deprecated_send_audio(client, dummy_data): + stub(responses.PUT, "https://api.nexmo.com/v1/calls/xx-xx-xx-xx/stream") + + assert isinstance( + client.send_audio("xx-xx-xx-xx", stream_url="http://example.com/audio.mp3"), + dict, + ) + assert request_user_agent() == dummy_data.user_agent + assert request_content_type() == "application/json" + assert request_body() == b'{"stream_url": "http://example.com/audio.mp3"}' + + +@responses.activate +def test_deprecated_stop_audio(client, dummy_data): + stub(responses.DELETE, "https://api.nexmo.com/v1/calls/xx-xx-xx-xx/stream") + + assert isinstance(client.stop_audio("xx-xx-xx-xx"), dict) + assert request_user_agent() == dummy_data.user_agent + + +@responses.activate +def test_deprecated_send_speech(client, dummy_data): + stub(responses.PUT, "https://api.nexmo.com/v1/calls/xx-xx-xx-xx/talk") + + assert isinstance(client.send_speech("xx-xx-xx-xx", text="Hello"), dict) + assert request_user_agent() == dummy_data.user_agent + assert request_content_type() == "application/json" + assert request_body() == b'{"text": "Hello"}' + + +@responses.activate +def test_deprecated_stop_speech(client, dummy_data): + stub(responses.DELETE, "https://api.nexmo.com/v1/calls/xx-xx-xx-xx/talk") + + assert isinstance(client.stop_speech("xx-xx-xx-xx"), dict) + assert request_user_agent() == dummy_data.user_agent + + +@responses.activate +def test_deprecated_send_dtmf(client, dummy_data): + stub(responses.PUT, "https://api.nexmo.com/v1/calls/xx-xx-xx-xx/dtmf") + + assert isinstance(client.send_dtmf("xx-xx-xx-xx", digits="1234"), dict) + assert request_user_agent() == dummy_data.user_agent + assert request_content_type() == "application/json" + assert request_body() == b'{"digits": "1234"}' + + +@responses.activate +def test_deprecated_user_provided_authorization(client, dummy_data): + stub(responses.GET, "https://api.nexmo.com/v1/calls/xx-xx-xx-xx") + + application_id = "different-nexmo-application-id" + nbf = int(time.time()) + exp = nbf + 3600 + + client.auth(application_id=application_id, nbf=nbf, exp=exp) + client.get_call("xx-xx-xx-xx") + + token = request_authorization().split()[1] + + token = jwt.decode(token, dummy_data.public_key, algorithm="RS256") + + assert token["application_id"] == application_id + assert token["nbf"] == nbf + assert token["exp"] == exp + + +@responses.activate +def test_deprecated_authorization_with_private_key_path(dummy_data): + stub(responses.GET, "https://api.nexmo.com/v1/calls/xx-xx-xx-xx") + + private_key = os.path.join(os.path.dirname(__file__), "data/private_key.txt") + + client = nexmo.Client( + key=dummy_data.api_key, + secret=dummy_data.api_secret, + application_id=dummy_data.application_id, + private_key=private_key, + ) + client.get_call("xx-xx-xx-xx") + + token = jwt.decode( + request_authorization().split()[1], dummy_data.public_key, algorithm="RS256" + ) + assert token["application_id"] == dummy_data.application_id + + +@responses.activate +def test_deprecated_authorization_with_private_key_object(client, dummy_data): + stub(responses.GET, "https://api.nexmo.com/v1/calls/xx-xx-xx-xx") + + client.get_call("xx-xx-xx-xx") + + token = jwt.decode( + request_authorization().split()[1], dummy_data.public_key, algorithm="RS256" + ) + assert token["application_id"] == dummy_data.application_id