diff --git a/.travis.yml b/.travis.yml index a7803a70da..fead52f55d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,24 @@ language: python python: - - "2.6" - - "2.7" - - "3.2" - - "3.3" - - "3.4" + - '2.6' + - '2.7' + - '3.2' + - '3.3' + - '3.4' + - '3.5' install: - - pip install . --use-mirrors - - pip install -r requirements.txt --use-mirrors - - pip install -r tests/requirements.txt --use-mirrors -script: + - pip install . + - pip install -r requirements.txt + - pip install -r tests/requirements.txt +script: - flake8 --ignore=F401 twilio - flake8 --ignore=E123,E126,E128,E501 tests - nosetests +sudo: false +notifications: + slack: + on_success: change + on_failure: change + rooms: + - secure: TcDlBcKXtqmMdVsa3Lsofdqc1uVjqhZouwNETC260rByRb74gTHGZ1Da7PRkv+AZIFUq7S1uWTZXTXJTm154hi4aQb9SE2UowVwTJMjIKyy4P79s1eoyADP92cFEcpqSs4iVpU3t5srTj8cg2fVks8EsV5pDVJut1oBH4qYJEZw= + - secure: qqtcwaS0y5e2SVm5g/zSRMgo7FcZ8Oa44bxQUDvJh/84/DHMD3zZoAv/A4+Vlbs0tCjnSKxMDuLxTzpiPgru4KPH7yj4fEXf7+sfwiLD//WBVWpGMYLa+PNCGS6hhnAuFkA2psZCmmzkbJbX0N03EdWiWFzV79WPgNI+WzpYIVQ= diff --git a/CHANGES.md b/CHANGES.md index 11b8e7dd71..1d9f2d150b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,114 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 5.6.0 +------------- + +Released September 19, 2016: + +- Add Video Grant + +Version 5.5.0 +------------- + +Released September 1, 2016: + +- Add Voice Grant + +Version 5.4.0 +------------- + +Released February 29, 2016: + +- Add support for API Key auth + +Version 5.3.0 +------------- + +Released January 28, 2016: + +- Add support for filter_friendly_name in WorkflowConfig +- Load reservations by default in TaskRouter + +Version 5.2.0 +------------- + +Released December 17, 2015: + +- Add support for IP Messaging + +Version 5.1.0 +------------- + +Released December 11, 2015: + +- Remove pyjwt dependency + +Version 5.0.0 +------------- + +Released December 8, 2015: + +- Update Access Tokens so that NBF is a optional parameter + +Version 4.10.0 +------------- + +Released December 3, 2015: + +- Add Access Tokens + +Version 4.9.2 +------------- + +Released November 25, 2015: + +- Fix for SIP Trunking bug + +Version 4.9.1 +------------- + +Released November 18, 2015: + +- Addresses bug with SMS Pricing country + +Version 4.9.0 +------------- + +Released November 3, 2015: + +- Add support for SIP Trunking + +Version 4.8.0 +------------- + +- Add support for SMS pricing + + +Version 4.6.0 +------------- + +- Add /Keys endpoint + +Version 4.6.0 +------------- + +Released September 23, 2015: + +- Allow fetching TaskRouter reservations by Worker +- Add missing Enqueue->Task TwiML generation +- Add Worflow construction + +Version 4.5.0 +------------- + +Released August 11, 2015: + +- Add support for new Taskrouter JWT Functionality, JWTs now grant access to + - Workspace + - Worker + - TaskQueue + Version 4.4.0 ------------- diff --git a/Makefile b/Makefile index 9f51e8826a..5b717bda7a 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ -.PHONY: clean venv install analysis test test-install docs docs-install +.PHONY: clean install analysis test test-install docs docs-install venv: virtualenv venv install: venv - . venv/bin/activate; pip install . --use-mirrors + . venv/bin/activate; pip install . test-install: install . venv/bin/activate; pip install -r tests/requirements.txt diff --git a/docs/appengine.rst b/docs/appengine.rst index 0cdf9db9c8..74a26a4fcd 100644 --- a/docs/appengine.rst +++ b/docs/appengine.rst @@ -102,7 +102,7 @@ The key is to lay out your project in a way that makes sense. the settings you want in Google App Engine - Note the folder path ends with ``twilio-demo/src``. - .. image:: https://www.evernote.com/shard/s265/sh/1b9407b0-c89b-464d-b352-dbf8fc7a7f41/f536b8e79747f43220fc12e0e0026ee2/res/5b2f83af-8a7f-451f-afba-db092c55aa44/skitch.png + .. image:: http://howtodocs.s3.amazonaws.com/helpers/appengine.png Once App Engine is running locally, in your browser, you should be able to navigate to ``http://localhost`` + the provided Port and view the twilio diff --git a/docs/usage/taskrouter.rst b/docs/usage/taskrouter.rst index 5915a4744e..76de468ed3 100644 --- a/docs/usage/taskrouter.rst +++ b/docs/usage/taskrouter.rst @@ -38,6 +38,71 @@ its unique ID. ) print workspace.sid +.. + +The following code will get an instance of an existing :class:`workspace` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + workspace = client.workspaces.get(WORKSPACE_SID) + print workspace.friendly_name +.. + +The following code will get the list of all existing :class:`workspace` resources + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + for workspace in client.workspaces.list() + print workspace.friendly_name +.. + +The following code will create a update an existing :class:`Workspace` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + workspace = client.workspaces.update( + WORKSPACE_SID, + friendly_name='Test Workspace', + event_callback_uri="http://www.example.com", + template='FIFO') + +.. +The following code will delete an existing :class:`workspace` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + client.workspaces.delete(WORKSPACE_SID) +.. + + Workflows --------- @@ -87,13 +152,139 @@ unique ID: client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) - workspace = client.workflows(WORKSPACE_SID).create( + workflow = client.workflows(WORKSPACE_SID).create( friendly_name="Incoming Call Flow", assignment_callback_url="https://example.com/callback", fallback_assignment_callback_url="https://example.com/callback2", configuration=CONFIG ) - print workspace.sid + print workflow.sid + +.. + +The following code will get a instance of an existing :class:`workflow` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + workflow = client.workflows(WORKSPACE_SID).get(WORKFLOW_SID) + print workflow.friendly_name + +.. + + + +The following code will get a list of all existing :class:`workflow` resources + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + for workflow in client.workflows(WORKSPACE_SID).list() + print workflow.friendly_name + +.. + +The following code will update an existing :class:`workflow` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + # Some JSON to configure the Workflow. See the documentation at + # http://www.twilio.com/docs/taskrouter for more details. + CONFIG = """ + { + "task_routing":{ + "filters":[ + { + "friendly_name":"Gold Tickets", + "expression":"customer_value == 'Gold' AND type == 'ticket'", + "targets":[ + { + "task_queue_sid":"WQ0123456789abcdef0123456789abcdef", + "priority":"2" + } + ] + }, + { + "targets": [ + { + "queue": "WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f", + "priority": "1" + } + ], + "friendly_name": "Marketing", + "expression": "type == 'marketing'" + } + ], + "default_filter":{ + "task_queue_sid":"WQabcdef01234567890123456789abcdef" + } + } + } + """ + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + workflow = client.workflows(WORKSPACE_SID).update( + WORKFLOW_SID, + friendly_name="Incoming Call Flow", + assignment_callback_url="https://example.com/callback", + fallback_assignment_callback_url="https://example.com/callback2", + configuration=CONFIG + ) + print workflow.sid + +.. + +The following code will delete an existing :class:`Workflow` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + client.workflows(WORKSPACE_SID).delete( + WORKFLOW_SID + ) + + +.. Activities @@ -122,6 +313,87 @@ To create a new :class:`Activity`: available=False, # Whether workers are available to handle tasks during this activity ) +.. + +To get an existing :class:`activity` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + activity = client.activities(WORKSPACE_SID).get(ACTIVITY_SID) + print activity.friendly_name + +.. + +To get a list of existing :class:`activity` resources + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + for activity in client.activities(WORKSPACE_SID).list() + print activity.friendly_name + +.. + +To update an existing :class:`Activity` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + activity = client.activities(WORKSPACE_SID).update( + ACTIVITY_SID, + friendly_name="Coffee Break", + available=True, + ) + +.. + +To delete an existing :class:`Activity` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + activity = client.activities(WORKSPACE_SID).delete( + ACTIVITY_SID + ) + +.. Workers ------- @@ -153,6 +425,92 @@ To create a new :class:`Worker`: ) print worker.sid +.. + +To get an existing :class:`worker` instance + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + worker = client.workers(WORKSPACE_SID).get(WORKER_SID) + print worker_friendly_name; +.. + + +To get an existing :class:`worker` list + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + for worker in client.workers(WORKSPACE_SID).list() + print worker_friendly_name; +.. + + +To update an existing :class:`Worker` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + worker = client.workers(WORKSPACE_SID).update( + WORKER_SID, + friendly_name="Jamie Howe", + attributes="""{ + "phone": "+14155551234", + "languages": ["EN", "ES","DE"] + } + """ + ) + print worker.sid + +.. + +To delete an existing :class:`Worker` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + client.workers(WORKSPACE_SID).delete( + WORKER_SID + ) + +.. TaskQueues ---------- @@ -186,6 +544,100 @@ To create a new :class:`TaskQueue`: ) print queue.sid +.. + +To get an existing :class`TaskQueue` instance + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + queue = client.task_queues(WORKSPACE_SID).get(TASKQUEUE_SID) + print queue.sid + +.. + + + +To get an existing :class`TaskQueue` list + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + for queue in client.task_queues(WORKSPACE_SID).list() + print queue.sid + +.. + + +To update an existing :class:`TaskQueue` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + queue = client.task_queues(WORKSPACE_SID).update( + TASKQUEUE_SID, + friendly_name="Sales+Pre-Sales", + # The Activity to assign workers when a task is reserved for them + reservation_activity_sid="WA11111111111", + # The Activity to assign workers when a task is assigned to them + assignment_activity_sid="WA222222222222", + ) + print queue.sid + +.. + +To delete an existing :class:`TaskQueue` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + queue = client.task_queues(WORKSPACE_SID).delete( + TASKQUEUE_SID + ) + print queue.sid + +.. + Tasks ----- @@ -223,3 +675,161 @@ To create a new :class:`Task` via the REST API: workflow_sid=WORKFLOW_SID ) print task.sid +.. + +To get an existing :class:`Task` instance + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + WORKFLOW_SID = "WWXXXXXXXXXXXXXX" + # Some JSON containing attributes for this task. User-defined. + TASK_ATTRIBUTES = """{ + "type": "call", + "contact": "+2014068777", + "customer-value": "gold", + "task-reason": "support", + "callSid": "CA42ed11..." + }""" + + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + task = client.tasks(WORKSPACE_SID).delete(TASK_SID) + print task.attributes +.. + + +To get an existing :class:`Task` list + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + WORKFLOW_SID = "WWXXXXXXXXXXXXXX" + # Some JSON containing attributes for this task. User-defined. + TASK_ATTRIBUTES = """{ + "type": "call", + "contact": "+2014068777", + "customer-value": "gold", + "task-reason": "support", + "callSid": "CA42ed11..." + }""" + + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + for task in client.tasks(WORKSPACE_SID).list() + print task.attributes +.. + +To update an existing :class:`Task` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + WORKFLOW_SID = "WWXXXXXXXXXXXXXX" + # Some JSON containing attributes for this task. User-defined. + TASK_ATTRIBUTES = """{ + "type": "call", + "contact": "+2014068777", + "customer-value": "gold", + "task-reason": "support", + "callSid": "CA42ed11..." + }""" + + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + task = client.tasks(WORKSPACE_SID).update( + TASK_SID, + attributes=TASK_ATTRIBUTES, + assignment_status='pending', + workflow_sid=WORKFLOW_SID + ) + print task.sid +.. + +To delete an existing :class:`Task` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + WORKFLOW_SID = "WWXXXXXXXXXXXXXX" + # Some JSON containing attributes for this task. User-defined. + TASK_ATTRIBUTES = """{ + "type": "call", + "contact": "+2014068777", + "customer-value": "gold", + "task-reason": "support", + "callSid": "CA42ed11..." + }""" + + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + client.tasks(WORKSPACE_SID).delete( + TASK_SID + ) + +.. + + +Using Workflow builder helper classes to create a :class:`Workflow` resource. + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + rules = [ + WorkflowRule("1==1", [WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20)],"SomeQ"), + WorkflowRule("1==1", [WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210)], "SomeOtherQ") + ] + default_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) + config = WorkflowConfig(rules, default_target) + print config.to_json() + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + workflow = client.workflows(WORKSPACE_SID).create( + friendly_name= "Incoming Call Flow", + assignment_callback_url= "https://example.com/callback", + fallback_assignment_callback_url= "https://example.com/callback2", + configuration= config.to_json() + ) + + print workflow.sid + + + +.. \ No newline at end of file diff --git a/docs/usage/twiml.rst b/docs/usage/twiml.rst index 6305f16c05..7ae32225ae 100644 --- a/docs/usage/twiml.rst +++ b/docs/usage/twiml.rst @@ -30,7 +30,7 @@ The output is perfectly formatted XML: .. code-block:: xml - Hello + Hello The verb methods (outlined in the :doc:`complete reference `) take the body (only text) of the verb as the first argument. @@ -49,7 +49,7 @@ All attributes are keyword arguments. https://api.twilio.com/cowbell.mp3 - + The Message and Sms verbs are slightly special: because :const:`from` is a Python keyword, use the :const:`sender` parameter to specify the number @@ -68,7 +68,7 @@ the message should come from: Hello MMS Monkey! - + Python 2.6+ added the :const:`with` statement for context management. Using :const:`with`, the module can *almost* emulate Ruby blocks. diff --git a/issue_template.md b/issue_template.md new file mode 100644 index 0000000000..c041708d80 --- /dev/null +++ b/issue_template.md @@ -0,0 +1,25 @@ +*Note: These issues are for bugs and feature requests for the helper libraries. If you need help or support, please email help@twilio.com and one of our experts will assist you!* + + +**Version:** +**API Subdomain (api/taskrouter/ip_messaging):** + +### Code Snippet +```python +# paste code here +``` + +### Exception / Log +``` + +``` + +### Steps to Reproduce +1. +2. +3. + + +### Feature Request +_If this is a feature request, make sure you search Issues for an existing request before creating a new one!_ + diff --git a/setup.py b/setup.py index 78f31fc2ae..69259aa554 100755 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ ':python_version=="3.2"': ['pysocks'], ':python_version=="3.3"': ['pysocks'], ':python_version=="3.4"': ['pysocks'], + ':python_version=="3.5"': ['pysocks'], }, packages = find_packages(), include_package_data=True, @@ -49,6 +50,7 @@ "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Communications :: Telephony", ], diff --git a/tests/ip_messaging/__init__.py b/tests/ip_messaging/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/ip_messaging/test_channels.py b/tests/ip_messaging/test_channels.py new file mode 100644 index 0000000000..90720cdd3a --- /dev/null +++ b/tests/ip_messaging/test_channels.py @@ -0,0 +1,54 @@ +import unittest +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Channels, Channel +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +CHANNEL_SID = "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Channels(BASE_URI, AUTH) + + +class ChannelTest(unittest.TestCase): + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_create_channel(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/channel_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Channels" % (BASE_URI) + list_resource.create(friendly_name='TestChannel', unique_name='Unique') + exp_params = { + 'FriendlyName': "TestChannel", + 'UniqueName': 'Unique' + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_get(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/channel_instance.json") + mock.return_value = resp + + uri = "%s/Channels/%s" % (BASE_URI, CHANNEL_SID) + list_resource.get(CHANNEL_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.Resource.request") + def test_delete(self, req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Channel(list_resource, "CH123") + app.delete() + uri = "%s/Channels/CH123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_credentials.py b/tests/ip_messaging/test_credentials.py new file mode 100644 index 0000000000..ae749db875 --- /dev/null +++ b/tests/ip_messaging/test_credentials.py @@ -0,0 +1,53 @@ +import unittest +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Credentials, Credential +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +CREDENTIAL_SID = "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Credentials(BASE_URI, AUTH) + + +class CredentialTest(unittest.TestCase): + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_create_credential(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/credential_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Credentials" % (BASE_URI) + list_resource.create('apn') + exp_params = { + 'Type': "apn" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_get(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/credential_instance.json") + mock.return_value = resp + + uri = "%s/Credentials/%s" % (BASE_URI, CREDENTIAL_SID) + list_resource.get(CREDENTIAL_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.Resource.request") + def test_delete(self, req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Credential(list_resource, "IS123") + app.delete() + uri = "https://ip-messaging.twilio.com/v1/Credentials/IS123" + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_members.py b/tests/ip_messaging/test_members.py new file mode 100644 index 0000000000..23010bbee3 --- /dev/null +++ b/tests/ip_messaging/test_members.py @@ -0,0 +1,53 @@ +import unittest +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Members, Member +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx/Channels/CHxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +MEMBER_SID = "MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Members(BASE_URI, AUTH) + + +class MemberTest(unittest.TestCase): + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_create_member(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/member_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Members" % (BASE_URI) + list_resource.create('test_identity') + exp_params = { + 'Identity': "test_identity" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_get(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/member_instance.json") + mock.return_value = resp + + uri = "%s/Members/%s" % (BASE_URI, MEMBER_SID) + list_resource.get(MEMBER_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.Resource.request") + def test_delete(self, req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Member(list_resource, "MB123") + app.delete() + uri = "%s/Members/MB123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_messages.py b/tests/ip_messaging/test_messages.py new file mode 100644 index 0000000000..49b5778225 --- /dev/null +++ b/tests/ip_messaging/test_messages.py @@ -0,0 +1,68 @@ +import unittest +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Messages, Message +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx/Channels/CHxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +MESSAGE_SID = "MSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Messages(BASE_URI, AUTH) + + +class MessageTest(unittest.TestCase): + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_create_message(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/message_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Messages" % (BASE_URI) + list_resource.create('TestBody') + exp_params = { + 'Body': "TestBody" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_get(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/message_instance.json") + mock.return_value = resp + + uri = "%s/Messages/%s" % (BASE_URI, MESSAGE_SID) + list_resource.get(MESSAGE_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_update(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/message_instance.json") + mock.return_value = resp + + update_params = { + 'UniqueName': 'unique' + } + + uri = "%s/Messages/%s" % (BASE_URI, MESSAGE_SID) + list_resource.update(MESSAGE_SID, unique_name='unique') + + mock.assert_called_with("POST", uri, data=update_params, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.Resource.request") + def test_delete(self, req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Message(list_resource, "MS123") + app.delete() + uri = "%s/Messages/MS123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_roles.py b/tests/ip_messaging/test_roles.py new file mode 100644 index 0000000000..b8485361ee --- /dev/null +++ b/tests/ip_messaging/test_roles.py @@ -0,0 +1,57 @@ +import unittest +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Roles, Role +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +ROLE_SID = "ROaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Roles(BASE_URI, AUTH) + + +class RoleTest(unittest.TestCase): + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_get_role(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/role_instance.json") + mock.return_value = resp + + uri = "%s/Roles/%s" % (BASE_URI, ROLE_SID) + list_resource.get(ROLE_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_create_role(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/role_instance.json") + resp.status_code = 201 + mock.return_value = resp + + list_resource.create("Test Role", "deployment", "createChannel") + uri = "%s/Roles" % (BASE_URI) + mock.assert_called_with( + "POST", + uri, + data={ + 'FriendlyName': 'Test Role', + 'Type': 'deployment', + 'Permission': 'createChannel' + }, + auth=AUTH, + use_json_extension=False + ) + + @patch("twilio.rest.resources.base.Resource.request") + def test_delete_role(self, req): + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Role(list_resource, "RO123") + app.delete() + uri = "%s/Roles/RO123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_services.py b/tests/ip_messaging/test_services.py new file mode 100644 index 0000000000..e8bd5cde85 --- /dev/null +++ b/tests/ip_messaging/test_services.py @@ -0,0 +1,53 @@ +import unittest +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Services, Service +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +SERVICE_SID = "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Services(BASE_URI, AUTH) + + +class ServiceTest(unittest.TestCase): + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_create_service(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/service_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Services" % (BASE_URI) + list_resource.create('TestService') + exp_params = { + 'FriendlyName': "TestService" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_get(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/service_instance.json") + mock.return_value = resp + + uri = "%s/Services/%s" % (BASE_URI, SERVICE_SID) + list_resource.get(SERVICE_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.Resource.request") + def test_delete(self, req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Service(list_resource, "IS123") + app.delete() + uri = "https://ip-messaging.twilio.com/v1/Services/IS123" + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_users.py b/tests/ip_messaging/test_users.py new file mode 100644 index 0000000000..7a92c15569 --- /dev/null +++ b/tests/ip_messaging/test_users.py @@ -0,0 +1,53 @@ +import unittest +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Users, User +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +USER_SID = "USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Users(BASE_URI, AUTH) + + +class UserTest(unittest.TestCase): + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_create_user(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/user_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Users" % (BASE_URI) + list_resource.create('test_id') + exp_params = { + 'Identity': "test_id" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_get(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/user_instance.json") + mock.return_value = resp + + uri = "%s/Users/%s" % (BASE_URI, USER_SID) + list_resource.get(USER_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.Resource.request") + def test_delete(self, req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = User(list_resource, "US123") + app.delete() + uri = "%s/Users/US123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/tests/pricing/test_messaging_countries.py b/tests/pricing/test_messaging_countries.py new file mode 100644 index 0000000000..8ea1431ee1 --- /dev/null +++ b/tests/pricing/test_messaging_countries.py @@ -0,0 +1,88 @@ +import unittest +from mock import patch +from nose.tools import assert_equal +from tests.tools import create_mock_json +from twilio.rest.resources.pricing.messaging_countries import ( + MessagingCountries +) + +AUTH = ("AC123", "token") +BASE_URI = "https://pricing.twilio.com/v1" + + +class MessagingCountriesTest(unittest.TestCase): + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_messaging_countries(self, request): + resp = create_mock_json( + 'tests/resources/pricing/messaging_countries_list.json') + resp.status_code = 200 + request.return_value = resp + + countries = MessagingCountries(BASE_URI + "/Messaging", AUTH) + result = countries.list() + + assert_equal(result[0].iso_country, "AT") + assert_equal(len(result), 2) + + request.assert_called_with( + "GET", + "{0}/Messaging/Countries".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + params={} + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_messaging_country(self, request): + resp = create_mock_json( + 'tests/resources/pricing/messaging_countries_instance.json') + resp.status_code = 200 + request.return_value = resp + + countries = MessagingCountries(BASE_URI + "/Messaging", AUTH) + result = countries.get('US') + + assert_equal(result.iso_country, "US") + assert_equal(result.price_unit, "usd") + assert_equal(result.outbound_sms_prices[0]['mcc'], "311") + assert_equal(result.outbound_sms_prices[0]['mnc'], "484") + assert_equal(result.outbound_sms_prices[0]['carrier'], "Verizon") + prices = result.outbound_sms_prices[0]['prices'] + + assert_equal(prices[0]['number_type'], "mobile") + assert_equal(prices[0]['base_price'], "0.0075") + assert_equal(prices[0]['current_price'], "0.0070") + + assert_equal(prices[1]['number_type'], "local") + assert_equal(prices[1]['base_price'], "0.0075") + assert_equal(prices[1]['current_price'], "0.0070") + + assert_equal(prices[2]['number_type'], "shortcode") + assert_equal(prices[2]['base_price'], "0.01") + assert_equal(prices[2]['current_price'], "0.01") + + assert_equal(prices[3]['number_type'], "toll-free") + assert_equal(prices[3]['base_price'], "0.0075") + assert_equal(prices[3]['current_price'], "0.0075") + + inbound_sms_prices = result.inbound_sms_prices + + assert_equal(inbound_sms_prices[0]['number_type'], "local") + assert_equal(inbound_sms_prices[0]['base_price'], "0.0075") + assert_equal(inbound_sms_prices[0]['current_price'], "0.0075") + + assert_equal(inbound_sms_prices[1]['number_type'], "shortcode") + assert_equal(inbound_sms_prices[1]['base_price'], "0.0075") + assert_equal(inbound_sms_prices[1]['current_price'], "0.005") + + assert_equal(inbound_sms_prices[2]['number_type'], "toll-free") + assert_equal(inbound_sms_prices[2]['base_price'], "0.0075") + assert_equal(inbound_sms_prices[2]['current_price'], "0.0075") + + request.assert_called_with( + "GET", + "{0}/Messaging/Countries/US".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + ) diff --git a/tests/requirements.txt b/tests/requirements.txt index 9262910394..f1df3797ed 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -3,6 +3,6 @@ mock==0.8.0 nose coverage nosexcover -flake8 +flake8==3.0.3 mccabe wheel>=0.22.0 diff --git a/tests/resources/ip_messaging/channel_instance.json b/tests/resources/ip_messaging/channel_instance.json new file mode 100644 index 0000000000..938a62013a --- /dev/null +++ b/tests/resources/ip_messaging/channel_instance.json @@ -0,0 +1,16 @@ +{ + "sid": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "service_sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "friendly_name": "update", + "unique_name": "unique", + "attributes": "", + "date_created": "2015-08-20T09:30:24Z", + "date_updated": "2015-08-20T09:30:24Z", + "created_by": "system", + "url": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "links": { + "members": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Member", + "messages": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Messages" + } +} diff --git a/tests/resources/ip_messaging/credential_instance.json b/tests/resources/ip_messaging/credential_instance.json new file mode 100644 index 0000000000..9b24940277 --- /dev/null +++ b/tests/resources/ip_messaging/credential_instance.json @@ -0,0 +1,8 @@ +{ + "account_sid":"ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sid":"CRaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "friendly_name":"MyApp APN Certificate", + "type":"apn", + "date_created":"2015-06-30T21:16:50Z", + "date_updated":"2015-07-30T21:16:50Z" +} diff --git a/tests/resources/ip_messaging/member_instance.json b/tests/resources/ip_messaging/member_instance.json new file mode 100644 index 0000000000..adc75ddcfe --- /dev/null +++ b/tests/resources/ip_messaging/member_instance.json @@ -0,0 +1,9 @@ +{ + "sid": "MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "channel_sid": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "id": "carl@twilio.com", + "role": "admin", + "url": "/v1/Spaces/SPxx/Channels/CHxx/Members/MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} diff --git a/tests/resources/ip_messaging/message_instance.json b/tests/resources/ip_messaging/message_instance.json new file mode 100644 index 0000000000..acbe53124f --- /dev/null +++ b/tests/resources/ip_messaging/message_instance.json @@ -0,0 +1,12 @@ +{ + "sid": "IMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "date_created": "2015-07-23T20:20:10Z", + "date_updated": "2015-07-23T20:20:10Z", + "was_edited": true, + "from": "carl@twilio.com", + "to": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "body": "Hello", + "url": "/v1/Spaces/SPxx/Channels/CHxx/Messages/IMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} diff --git a/tests/resources/ip_messaging/role_instance.json b/tests/resources/ip_messaging/role_instance.json new file mode 100644 index 0000000000..bbd604428c --- /dev/null +++ b/tests/resources/ip_messaging/role_instance.json @@ -0,0 +1,11 @@ +{ + "sid":"RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid":"ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "name":"Deployment Admin", + "type":"deployment", + "permissions":[ + "createChannel", + "destroyChannel" + ] +} diff --git a/tests/resources/ip_messaging/service_instance.json b/tests/resources/ip_messaging/service_instance.json new file mode 100644 index 0000000000..bc4d56e4a9 --- /dev/null +++ b/tests/resources/ip_messaging/service_instance.json @@ -0,0 +1,17 @@ +{ + "sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "friendly_name": "TestService", + "date_created": "2015-10-21T04:15:36Z", + "date_updated": "2015-10-21T04:15:36Z", + "default_service_role_sid": "RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "default_channel_role_sid": "RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "typing_indicator_timeout": 5, + "webhooks": {}, + "url": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "links": { + "channels": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels", + "roles": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Roles", + "users": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Users" + } +} diff --git a/tests/resources/ip_messaging/user_instance.json b/tests/resources/ip_messaging/user_instance.json new file mode 100644 index 0000000000..a2326cc20c --- /dev/null +++ b/tests/resources/ip_messaging/user_instance.json @@ -0,0 +1,10 @@ +{ + "sid": "USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "date_created": "2015-08-19T18:18:00Z", + "date_updated": "2015-08-19T18:18:00Z", + "identity": "carl@twilio.com", + "service_sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "role_sid": null, + "url": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Users/USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} diff --git a/tests/resources/keys_instance.json b/tests/resources/keys_instance.json new file mode 100644 index 0000000000..86459b71ec --- /dev/null +++ b/tests/resources/keys_instance.json @@ -0,0 +1,6 @@ +{ + "sid":"SKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "friendly_name":"Fuzzy Lumpkins' SigningKey", + "date_created":"Fri, 13 Mar 2015 13:24:01 +0000", + "date_updated":"Fri, 13 Mar 2015 13:24:01 +0000" +} diff --git a/tests/resources/keys_list.json b/tests/resources/keys_list.json new file mode 100644 index 0000000000..e82306cca8 --- /dev/null +++ b/tests/resources/keys_list.json @@ -0,0 +1,18 @@ +{ + "keys":[ + { + "sid":"SK932e398ac43ca670b1609b05ee301e8c", + "friendly_name":"Fuzzy Lumpkins' SigningKey", + "date_created":"Fri, 13 Mar 2015 13:24:01 +0000", + "date_updated":"Fri, 13 Mar 2015 13:24:01 +0000" + } + ], + "first_page_uri":"/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Keys.json?PageSize=50&Page=0", + "uri":"/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Keys.json?PageSize=50&Page=0", + "next_page_uri":null, + "previous_page_uri":null, + "page":0, + "page_size":50, + "start":0, + "end":1 +} diff --git a/tests/resources/pricing/messaging_countries_instance.json b/tests/resources/pricing/messaging_countries_instance.json new file mode 100644 index 0000000000..2df45d006f --- /dev/null +++ b/tests/resources/pricing/messaging_countries_instance.json @@ -0,0 +1,51 @@ +{ + "country": "United States", + "iso_country": "US", + "price_unit": "usd", + "outbound_sms_prices": [ + { + "mcc": "311", + "mnc": "484", + "carrier": "Verizon", + "prices": [ + { + "number_type": "mobile", + "base_price": "0.0075", + "current_price": "0.0070" + }, + { + "number_type": "local", + "base_price": "0.0075", + "current_price": "0.0070" + }, + { + "number_type": "shortcode", + "base_price": "0.01", + "current_price": "0.01" + }, + { + "number_type": "toll-free", + "base_price": "0.0075", + "current_price": "0.0075" + } + ] + } + ], + "inbound_sms_prices": [ + { + "number_type": "local", + "base_price": "0.0075", + "current_price": "0.0075" + }, + { + "number_type": "shortcode", + "base_price": "0.0075", + "current_price": "0.005" + }, + { + "number_type": "toll-free", + "base_price": "0.0075", + "current_price": "0.0075" + } + ] +} \ No newline at end of file diff --git a/tests/resources/pricing/messaging_countries_list.json b/tests/resources/pricing/messaging_countries_list.json new file mode 100644 index 0000000000..07623348ec --- /dev/null +++ b/tests/resources/pricing/messaging_countries_list.json @@ -0,0 +1,23 @@ +{ + "meta": { + "first_page_url": "https://pricing.twilio.com/v1/Messaging/Countries?PageSize=50&Page=0", + "key": "countries", + "next_page_url": "https://pricing.twilio.com/v1/Messaging/Countries?PageSize=50&Page=1&PageToken=DNCZ", + "page": 0, + "page_size": 50, + "previous_page_url": null, + "url": "https://pricing.twilio.com/v1/Messaging/Countries?PageSize=50&Page=0" + }, + "countries": [ + { + "country": "Austria", + "iso_country": "AT", + "url": "https://pricing.twilio.com/v1/Messaging/Countries/AT" + }, + { + "country": "Australia", + "iso_country": "AU", + "url": "https://pricing.twilio.com/v1/Messaging/Countries/AU" + } + ] +} \ No newline at end of file diff --git a/tests/resources/trunking/credential_lists_instance.json b/tests/resources/trunking/credential_lists_instance.json new file mode 100644 index 0000000000..3f36eb2c30 --- /dev/null +++ b/tests/resources/trunking/credential_lists_instance.json @@ -0,0 +1,9 @@ +{ + "sid": "CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trunk_sid" : "TK11111111111111111111111111111111", + "friendly_name": "Test", + "date_created": "2015-01-02T11:23:45Z", + "date_updated": "2015-01-02T11:23:45Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} \ No newline at end of file diff --git a/tests/resources/trunking/credential_lists_list.json b/tests/resources/trunking/credential_lists_list.json new file mode 100644 index 0000000000..c284afd308 --- /dev/null +++ b/tests/resources/trunking/credential_lists_list.json @@ -0,0 +1,22 @@ +{ + "meta": { + "page": 0, + "page_size": 50, + "first_page_url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists?PageSize=50&Page=0", + "previous_page_url": null, + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists?PageSize=50&Page=0", + "next_page_url": null, + "key": "credential_lists" + }, + "credential_lists": [ + { + "sid": "CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trunk_sid": "TK11111111111111111111111111111111", + "friendly_name": "Test list", + "date_created": "2015-05-14T21:00:12Z", + "date_updated": "2015-05-14T21:00:12Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + ] +} \ No newline at end of file diff --git a/tests/resources/trunking/ip_access_control_lists_instance.json b/tests/resources/trunking/ip_access_control_lists_instance.json new file mode 100644 index 0000000000..9959d4bdec --- /dev/null +++ b/tests/resources/trunking/ip_access_control_lists_instance.json @@ -0,0 +1,9 @@ +{ + "sid": "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trunk_sid": "TK11111111111111111111111111111111", + "friendly_name": "Test", + "date_created": "2014-10-30T23:59:12Z", + "date_updated": "2014-10-30T23:59:12Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} \ No newline at end of file diff --git a/tests/resources/trunking/ip_access_control_lists_list.json b/tests/resources/trunking/ip_access_control_lists_list.json new file mode 100644 index 0000000000..07d91fdd13 --- /dev/null +++ b/tests/resources/trunking/ip_access_control_lists_list.json @@ -0,0 +1,22 @@ +{ + "ip_access_control_lists": [ + { + "sid": "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trunk_sid": "TK11111111111111111111111111111111", + "friendly_name": "Test", + "date_created": "2014-10-30T23:59:12Z", + "date_updated": "2014-10-30T23:59:12Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + ], + "meta": { + "page": 0, + "page_size": 50, + "first_page_url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists?PageSize=50&Page=0", + "previous_page_url": null, + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists?PageSize=50&Page=0", + "next_page_url": null, + "key": "ip_access_control_lists" + } +} \ No newline at end of file diff --git a/tests/resources/trunking/origination_urls_instance.json b/tests/resources/trunking/origination_urls_instance.json new file mode 100644 index 0000000000..27087bcc98 --- /dev/null +++ b/tests/resources/trunking/origination_urls_instance.json @@ -0,0 +1,13 @@ +{ + "sid": "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trunk_sid": "TK11111111111111111111111111111111", + "weight": 10, + "enabled": true, + "sip_url": "sip:169.10.1.35", + "friendly_name": "Name", + "priority": 20, + "date_created": "2015-09-02T23:15:56Z", + "date_updated": "2015-09-02T23:15:56Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} diff --git a/tests/resources/trunking/origination_urls_list.json b/tests/resources/trunking/origination_urls_list.json new file mode 100644 index 0000000000..43a41e7419 --- /dev/null +++ b/tests/resources/trunking/origination_urls_list.json @@ -0,0 +1,26 @@ +{ + "meta": { + "page": 0, + "page_size": 50, + "first_page_url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls?PageSize=50&Page=0", + "previous_page_url": null, + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls?PageSize=50&Page=0", + "next_page_url": null, + "key": "origination_urls" + }, + "origination_urls": [ + { + "sid": "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trunk_sid": "TK11111111111111111111111111111111", + "weight": 10, + "enabled": true, + "sip_url": "sip:169.10.1.35", + "friendly_name": "Name", + "priority": 20, + "date_created": "2015-09-02T23:15:56Z", + "date_updated": "2015-09-02T23:15:56Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + ] +} \ No newline at end of file diff --git a/tests/resources/trunking/phone_numbers_instance.json b/tests/resources/trunking/phone_numbers_instance.json new file mode 100644 index 0000000000..3a8e59e6b0 --- /dev/null +++ b/tests/resources/trunking/phone_numbers_instance.json @@ -0,0 +1,34 @@ +{ + "sid": "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "date_created": "2014-10-07T13:37:19Z", + "date_updated": "2015-09-02T16:27:22Z", + "friendly_name": "Name", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "phone_number": "+14158675309", + "api_version": "2010-04-01", + "voice_caller_id_lookup": false, + "voice_url": null, + "voice_method": null, + "voice_fallback_url": "", + "voice_fallback_method": "POST", + "status_callback": "", + "status_callback_method": "POST", + "voice_application_sid": null, + "trunk_sid": "TK11111111111111111111111111111111", + "sms_url": "https://demo.twilio.com/welcome/sms/reply/", + "sms_method": "POST", + "sms_fallback_url": "", + "sms_fallback_method": "POST", + "sms_application_sid": "", + "address_requirements": "none", + "beta": false, + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "capabilities": { + "voice": true, + "sms": true, + "mms": true + }, + "links": { + "phone_number": "https://api.twilio.com/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/IncomingPhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } +} \ No newline at end of file diff --git a/tests/resources/trunking/phone_numbers_list.json b/tests/resources/trunking/phone_numbers_list.json new file mode 100644 index 0000000000..d24d62195c --- /dev/null +++ b/tests/resources/trunking/phone_numbers_list.json @@ -0,0 +1,47 @@ +{ + "phone_numbers": [ + { + "sid": "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "date_created": "2014-10-07T13:37:19Z", + "date_updated": "2015-09-02T16:27:22Z", + "friendly_name": "Name", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "phone_number": "+14158675309", + "api_version": "2010-04-01", + "voice_caller_id_lookup": false, + "voice_url": null, + "voice_method": null, + "voice_fallback_url": "", + "voice_fallback_method": "POST", + "status_callback": "", + "status_callback_method": "POST", + "voice_application_sid": null, + "trunk_sid": "TK11111111111111111111111111111111", + "sms_url": "https://demo.twilio.com/welcome/sms/reply/", + "sms_method": "POST", + "sms_fallback_url": "", + "sms_fallback_method": "POST", + "sms_application_sid": "", + "address_requirements": "none", + "beta": false, + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "capabilities": { + "voice": true, + "sms": true, + "mms": true + }, + "links": { + "phone_number": "https://api.twilio.com/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/IncomingPhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + } + ], + "meta": { + "page": 0, + "page_size": 50, + "first_page_url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers?PageSize=50&Page=0", + "previous_page_url": null, + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers?PageSize=50&Page=0", + "next_page_url": null, + "key": "phone_numbers" + } +} \ No newline at end of file diff --git a/tests/resources/trunking/trunks_instance.json b/tests/resources/trunking/trunks_instance.json new file mode 100644 index 0000000000..1d23a71baa --- /dev/null +++ b/tests/resources/trunking/trunks_instance.json @@ -0,0 +1,26 @@ +{ + "sid": "TK11111111111111111111111111111111", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "domain_name": "test-trunk.pstn.twilio.com", + "disaster_recovery_method": null, + "disaster_recovery_url": null, + "friendly_name": "Test", + "secure": false, + "recording": { + "trim": "do-not-trim", + "mode": "record-from-ringing" + }, + "auth_type": "CREDENTIAL_LIST", + "auth_type_set": [ + "CREDENTIAL_LIST" + ], + "date_created": "2015-05-05T20:59:07Z", + "date_updated": "2015-05-05T22:44:23Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111", + "links": { + "origination_urls": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls", + "credential_lists": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists", + "ip_access_control_lists": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists", + "phone_numbers": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers" + } +} \ No newline at end of file diff --git a/tests/resources/trunking/trunks_list.json b/tests/resources/trunking/trunks_list.json new file mode 100644 index 0000000000..b2c238e143 --- /dev/null +++ b/tests/resources/trunking/trunks_list.json @@ -0,0 +1,39 @@ +{ + "trunks": [ + { + "sid": "TK11111111111111111111111111111111", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "domain_name": "test-trunk.pstn.twilio.com", + "disaster_recovery_method": null, + "disaster_recovery_url": null, + "friendly_name": "Test", + "secure": false, + "recording": { + "trim": "do-not-trim", + "mode": "record-from-ringing" + }, + "auth_type": "CREDENTIAL_LIST", + "auth_type_set": [ + "CREDENTIAL_LIST" + ], + "date_created": "2015-05-05T20:59:07Z", + "date_updated": "2015-05-05T22:44:23Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111", + "links": { + "origination_urls": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls", + "credential_lists": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists", + "ip_access_control_lists": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists", + "phone_numbers": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers" + } + } + ], + "meta": { + "page": 0, + "page_size": 50, + "first_page_url": "https://trunking.twilio.com/v1/Trunks?PageSize=50&Page=0", + "previous_page_url": null, + "url": "https://trunking.twilio.com/v1/Trunks?PageSize=50&Page=0", + "next_page_url": null, + "key": "trunks" + } +} \ No newline at end of file diff --git a/tests/task_router/test_capability.py b/tests/task_router/test_capability.py index abfb25b0f5..70a5e46bb1 100644 --- a/tests/task_router/test_capability.py +++ b/tests/task_router/test_capability.py @@ -55,8 +55,7 @@ def test_defaults(self): self.assertTrue(decoded is not None) websocket_url = ( - 'https://event-bridge.twilio.com/v1/wschannels/%s/%s' % - (self.account_sid, self.worker_sid) + 'https://event-bridge.twilio.com/v1/wschannels/{0}/{1}'.format(self.account_sid, self.worker_sid) ) expected = [ { @@ -74,13 +73,33 @@ def test_defaults(self): 'post_filter': {}, }, { - 'url': - 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities', + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities', 'method': 'GET', 'allow': True, 'query_filter': {}, 'post_filter': {}, }, + { + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**'.format(self.workspace_sid), + 'method': 'GET', + 'allow': True, + 'query_filter': {}, + 'post_filter': {}, + }, + { + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}/Reservations/**'.format(self.workspace_sid, self.worker_sid), + 'method': 'GET', + 'allow': True, + 'query_filter': {}, + 'post_filter': {}, + }, + { + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}'.format(self.workspace_sid, self.worker_sid), + 'method': 'GET', + 'allow': True, + 'query_filter': {}, + 'post_filter': {}, + } ] self.assertEqual(expected, decoded['policies']) @@ -90,7 +109,7 @@ def test_allow_worker_activity_updates(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s' % ( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}'.format( self.workspace_sid, self.worker_sid, ) @@ -110,7 +129,7 @@ def test_allow_worker_fetch_attributes(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s' % ( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}'.format( self.workspace_sid, self.worker_sid, ) @@ -131,7 +150,7 @@ def test_allow_task_reservation_updates(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**' % ( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**'.format( self.workspace_sid, ) @@ -140,6 +159,6 @@ def test_allow_task_reservation_updates(self): 'method': 'POST', 'allow': True, 'query_filter': {}, - 'post_filter': {'ReservationStatus': {'required': True}}, + 'post_filter': {}, } self.assertEqual(expected, decoded['policies'][-1]) diff --git a/tests/task_router/test_reservations.py b/tests/task_router/test_reservations.py index ff85ffa316..5d635dad9a 100644 --- a/tests/task_router/test_reservations.py +++ b/tests/task_router/test_reservations.py @@ -8,6 +8,7 @@ AUTH = ("AC123", "token") BASE_URI = "https://taskrouter.twilio.com/v1/Accounts/AC123/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Tasks/WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +BASE_WORKER_URI = "https://taskrouter.twilio.com/v1/Accounts/AC123/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Workers/WKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" RESERVATION_SID = "WRaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" @@ -36,6 +37,18 @@ def test_list(self, request): request.assert_called_with("GET", uri, params={}, auth=AUTH, use_json_extension=False) + @patch('twilio.rest.resources.base.make_twilio_request') + def test_list_for_worker(self, request): + resp = create_mock_json('tests/resources/task_router/reservations_list.json') + resp.status_code = 200 + request.return_value = resp + + uri = "{0}/Reservations".format(BASE_WORKER_URI) + list_resource = Reservations(BASE_WORKER_URI, AUTH) + list_resource.list() + request.assert_called_with("GET", uri, params={}, auth=AUTH, + use_json_extension=False) + @patch('twilio.rest.resources.base.make_twilio_request') def test_update_instance(self, request): resp = create_mock_json('tests/resources/task_router/reservations_instance.json') diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py new file mode 100644 index 0000000000..e1d67efad9 --- /dev/null +++ b/tests/task_router/test_task_router_capability.py @@ -0,0 +1,168 @@ +import unittest +import warnings + +from twilio import jwt +from twilio.task_router import TaskRouterCapability + + +class TaskRouterCapabilityTest(unittest.TestCase): + + def check_policy(self, method, url, policy): + self.assertEqual(url, policy['url']) + self.assertEqual(method, policy['method']) + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["channel"], channel_id) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], channel_id) + + if 'worker_sid' in decoded.keys(): + self.assertEqual(decoded['worker_sid'], channel_sid) + if 'taskqueue_sid' in decoded.keys(): + self.assertEqual(decoded['taskqueue_sid'], channel_sid) + + def test_workspace_default(self): + account_sid = "AC123" + auth_token = "foobar" + workspace_sid = "WS456" + channel_id = "WS456" + capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, channel_id) + + capability.generate_token() + + token = capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, auth_token) + self.assertNotEqual(None, decoded) + + self.check_decoded(decoded, account_sid, workspace_sid, channel_id) + + policies = decoded['policies'] + self.assertEqual(len(policies), 3) + + for method, url, policy in [ + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[0]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456", policies[2]), + ]: + yield self.check_policy, method, url, policy + + def test_worker_default(self): + account_sid = "AC123" + auth_token = "foobar" + workspace_sid = "WS456" + worker_sid = "WK789" + capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, worker_sid) + + capability.generate_token() + + token = capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, auth_token) + self.assertNotEqual(None, decoded) + + self.check_decoded(decoded, account_sid, workspace_sid, worker_sid, worker_sid) + + policies = decoded['policies'] + self.assertEqual(len(policies), 6) + + for method, url, policy in [ + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[0]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[2]) + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[3]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[4]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789/Reservations/**", policies[5]) + ]: + yield self.check_policy, method, url, policy + + def test_task_queue_default(self): + account_sid = "AC123" + auth_token = "foobar" + workspace_sid = "WS456" + taskqueue_sid = "WQ789" + capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, taskqueue_sid) + + capability.generate_token() + + token = capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, auth_token) + self.assertNotEqual(None, decoded) + + self.check_decoded(decoded, account_sid, workspace_sid, taskqueue_sid, taskqueue_sid) + + policies = decoded['policies'] + self.assertEqual(len(policies), 3) + + for method, url, policy in [ + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", policies[0]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", policies[1]) + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", policies[2]) + ]: + yield self.check_policy, method, url, policy + + def test_deprecated_worker(self): + account_sid = "AC123" + auth_token = "foobar" + workspace_sid = "WS456" + worker_sid = "WK789" + capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, worker_sid) + + capability.generate_token() + + token = capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, auth_token) + self.assertNotEqual(None, decoded) + + self.check_decoded(decoded, account_sid, workspace_sid, worker_sid, worker_sid) + + policies = decoded['policies'] + self.assertEqual(len(policies), 6) + + # should expect 6 policies + for method, url, policy in [ + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[0]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[2]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[3]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789/Reservations/**", policies[4]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[5]) + ]: + yield self.check_policy, method, url, policy + + # check deprecated warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + capability.allow_worker_fetch_attributes() + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated" in str(w[-1].message) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + capability.allow_worker_activity_updates() + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated" in str(w[-1].message) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + capability.allow_task_reservation_updates() + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated" in str(w[-1].message) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/task_router/test_task_router_taskqueue_capability.py b/tests/task_router/test_task_router_taskqueue_capability.py new file mode 100644 index 0000000000..42ed9597e6 --- /dev/null +++ b/tests/task_router/test_task_router_taskqueue_capability.py @@ -0,0 +1,132 @@ +import time +import unittest + +from twilio import jwt +from twilio.task_router import TaskRouterTaskQueueCapability + + +class TaskRouterTaskQueueCapabilityTest(unittest.TestCase): + + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.taskqueue_sid = "WQ789" + self.capability = TaskRouterTaskQueueCapability(self.account_sid, self.auth_token, self.workspace_sid, self.taskqueue_sid) + + def test_generate_token(self): + + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + self.assertEqual(decoded["iss"], self.account_sid) + self.assertEqual(decoded["account_sid"], self.account_sid) + self.assertEqual(decoded["workspace_sid"], self.workspace_sid) + self.assertEqual(decoded["taskqueue_sid"], self.taskqueue_sid) + self.assertEqual(decoded["channel"], self.taskqueue_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], self.taskqueue_sid) + + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + + def test_generate_token_with_custom_ttl(self): + ttl = 10000 + + token = self.capability.generate_token(ttl) + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + + def test_default(self): + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 3) + + # websocket GET + get_policy = policies[0] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allow']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) + + # websocket POST + post_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allow']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) + + # fetch GET + fetch_policy = policies[2] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allow']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) + + def test_allow_fetch_subresources(self): + self.capability.allow_fetch_subresources() + + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # confirm the additional policy generated with allow_fetch_subresources() + + policy = policies[3] + + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**") + self.assertEqual(policy['method'], "GET") + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + + def test_allow_updates_subresources(self): + self.capability.allow_updates_subresources() + + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # confirm the additional policy generated with allow_updates_subresources() + + policy = policies[3] + + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**") + self.assertEqual(policy['method'], "POST") + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py new file mode 100644 index 0000000000..97ac7d0160 --- /dev/null +++ b/tests/task_router/test_task_router_worker_capability.py @@ -0,0 +1,136 @@ +import time +import unittest + +from twilio import jwt +from twilio.task_router import TaskRouterWorkerCapability + + +class TaskRouterWorkerCapabilityTest(unittest.TestCase): + def check_policy(self, method, url, policy): + self.assertEqual(url, policy['url']) + self.assertEqual(method, policy['method']) + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["channel"], channel_id) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], channel_id) + + if 'worker_sid' in decoded.keys(): + self.assertEqual(decoded['worker_sid'], channel_sid) + if 'taskqueue_sid' in decoded.keys(): + self.assertEqual(decoded['taskqueue_sid'], channel_sid) + + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.worker_sid = "WK789" + self.capability = TaskRouterWorkerCapability(self.account_sid, self.auth_token, self.workspace_sid, self.worker_sid) + + def test_generate_token(self): + + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + self.check_decoded(decoded, self.account_sid, self.workspace_sid, self.worker_sid, self.worker_sid) + + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + + def test_generate_token_with_custom_ttl(self): + ttl = 10000 + + token = self.capability.generate_token(ttl) + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + + def test_defaults(self): + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/{0}/{1}'.format(self.account_sid, self.worker_sid) + + # expect 6 policies + policies = decoded['policies'] + self.assertEqual(len(policies), 6) + + # should expect 6 policies + for method, url, policy in [ + ('GET', websocket_url, policies[0]), + ('POST', websocket_url, policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[2]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[3]) + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[4]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789/Reservations/**", policies[5]) + ]: + yield self.check_policy, method, url, policy + + def test_allow_activity_updates(self): + + # allow activity updates to the worker + self.capability.allow_activity_updates() + + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 7) + policy = policies[6] + + url = "https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}".format(self.workspace_sid, self.worker_sid) + + self.assertEqual(url, policy["url"]) + self.assertEqual("POST", policy["method"]) + self.assertTrue(policy["allow"]) + self.assertNotEqual(None, policy['post_filter']) + self.assertEqual({}, policy['query_filter']) + self.assertTrue(policy['post_filter']['ActivitySid']) + + def test_allow_reservation_updates(self): + # allow reservation updates + self.capability.allow_reservation_updates() + + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 8) + + taskPolicy = policies[6] + tasksUrl = "https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**".format(self.workspace_sid) + self.check_policy('POST', tasksUrl, taskPolicy) + + workerReservationsPolicy = policies[7] + reservationsUrl = "https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}/Reservations/**".format(self.workspace_sid, self.worker_sid) + self.check_policy('POST', reservationsUrl, workerReservationsPolicy) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/task_router/test_task_router_workspace_capability.py b/tests/task_router/test_task_router_workspace_capability.py new file mode 100644 index 0000000000..048611166e --- /dev/null +++ b/tests/task_router/test_task_router_workspace_capability.py @@ -0,0 +1,117 @@ +import time +import unittest + +from twilio import jwt +from twilio.task_router import TaskRouterWorkspaceCapability + + +class TaskRouterWorkspaceCapabilityTest(unittest.TestCase): + def check_policy(self, method, url, policy): + self.assertEqual(url, policy['url']) + self.assertEqual(method, policy['method']) + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["channel"], channel_id) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], channel_id) + + if 'worker_sid' in decoded.keys(): + self.assertEqual(decoded['worker_sid'], channel_sid) + if 'taskqueue_sid' in decoded.keys(): + self.assertEqual(decoded['taskqueue_sid'], channel_sid) + + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.capability = TaskRouterWorkspaceCapability(self.account_sid, self.auth_token, self.workspace_sid) + + def test_generate_token(self): + + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + self.check_decoded(decoded, self.account_sid, self.workspace_sid, self.workspace_sid) + + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + + def test_generate_token_with_custom_ttl(self): + ttl = 10000 + + token = self.capability.generate_token(ttl) + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + + def test_default(self): + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 3) + + for method, url, policy in [ + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[0]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456", policies[2]) + ]: + yield self.check_policy, method, url, policy + + def test_allow_fetch_subresources(self): + self.capability.allow_fetch_subresources() + + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # confirm the additional policy generated with allow_fetch_subresources() + policy = policies[3] + + self.check_policy('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/**", policy) + + def test_allow_updates_subresources(self): + self.capability.allow_updates_subresources() + + token = self.capability.generate_token() + self.assertNotEqual(None, token) + + decoded = jwt.decode(token, self.auth_token) + self.assertNotEqual(None, decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # confirm the additional policy generated with allow_updates_subresources() + policy = policies[3] + + self.check_policy('POST', "https://taskrouter.twilio.com/v1/Workspaces/WS456/**", policy) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py new file mode 100644 index 0000000000..4aa99213de --- /dev/null +++ b/tests/task_router/test_workflow_config.py @@ -0,0 +1,284 @@ +import unittest +import json + +from twilio.task_router.workflow_config import WorkflowConfig +from twilio.task_router.workflow_rule import WorkflowRule +from twilio.task_router.workflow_ruletarget import WorkflowRuleTarget + + +class WorkflowConfigTest(unittest.TestCase): + def test_to_json(self): + rules = [ + WorkflowRule("1==1", [WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20)], "SomeQ"), + WorkflowRule("1==1", [WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210)], "SomeOtherQ") + ] + def_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) + config = WorkflowConfig(rules, def_target) + self.assertEqual(True, self.is_json(config.to_json())) + + def test_from_json(self): + + data = { + 'task_routing': + { + 'filters': [ + { + 'expression': 'type == "sales"', + 'friendly_name': 'Sales', + 'targets': [ + { + 'queue': 'WQec62de0e1148b8477f2e24579779c8b1', + 'expression': 'task.language IN worker.languages' + } + ] + }, + { + 'expression': 'type == "marketing"', + 'friendly_name': 'Marketing', + 'targets': [ + { + 'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f', + 'expression': 'task.language IN worker.languages' + } + ] + }, + { + 'expression': 'type == "support"', + 'friendly_name': 'Support', + 'targets': [ + { + 'queue': 'WQe5eb317eb23500ade45087ea6522896c', + 'expression': 'task.language IN worker.languages' + } + ] + } + ], + 'default_filter': + { + 'queue': 'WQ05f810d2d130344fd56e3c91ece2e594' + } + } + } + + config = WorkflowConfig.json2obj(json.dumps(data)) + self.assertEqual(3, len(config.task_routing.filters)) + self.assertEqual(1, len(config.task_routing.default_filter)) + + def test_from_json2(self): + + data = {'task_routing': {'filters': [{'friendly_name': 'SomeQ', 'expression': '1==1', 'targets': [ + {'priority': 1, 'queue': 'WQXXXX', 'expression': '1==1', 'timeout': 20}]}, + {'friendly_name': 'SomeOtherQ', 'expression': '1==1', + 'targets': [ + {'priority': 1, 'queue': 'WQXXXX', 'expression': '1==1', + 'timeout': 20}]}], + 'default_filter': {'priority': None, 'queue': 'WQYYYYY', 'expression': None, + 'timeout': None}}} + config = WorkflowConfig.json2obj(json.dumps(data)) + self.assertEqual(2, len(config.task_routing.filters)) + self.assertEqual(4, len(config.task_routing.default_filter)) + + def test_from_json_with_filter_friendly_name(self): + data = { + 'task_routing': + { + 'filters': [ + { + 'expression': 'type == "sales"', + 'filter_friendly_name': 'Sales', + 'targets': [ + { + + 'queue': 'WQec62de0e1148b8477f2e24579779c8b1', + 'expression': 'task.language IN worker.languages' + } + ] + }, + { + 'expression': 'type == "marketing"', + 'filter_friendly_name': 'Marketing', + 'targets': [ + { + 'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f', + 'expression': 'task.language IN worker.languages' + } + ] + }, + { + 'expression': 'type == "support"', + 'filter_friendly_name': 'Support', + 'targets': [ + { + 'queue': 'WQe5eb317eb23500ade45087ea6522896c', + 'expression': 'task.language IN worker.languages' + } + ] + } + ], + 'default_filter': + { + 'queue': 'WQ05f810d2d130344fd56e3c91ece2e594' + } + } + } + # marshal object + config = WorkflowConfig.json2obj(json.dumps(data)) + self.assertEqual(3, len(config.task_routing.filters)) + self.assertEqual(1, len(config.task_routing.default_filter)) + + # check that the configuration was marshaled to "friendly_name" and not "filter_friendly_name" + expected_config_data = { + "task_routing": + { + "default_filter": + { + "queue": "WQ05f810d2d130344fd56e3c91ece2e594" + }, + "filters": [ + { + "expression": "type == \"sales\"", + "friendly_name": "Sales", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQec62de0e1148b8477f2e24579779c8b1" + } + ] + }, + { + "expression": "type == \"marketing\"", + "friendly_name": "Marketing", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f" + } + ] + }, + { + "expression": "type == \"support\"", + "friendly_name": "Support", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQe5eb317eb23500ade45087ea6522896c" + } + ] + } + ] + } + } + + expected_config_json = json.dumps(expected_config_data, + sort_keys=True, + indent=4) + # check that marshaling back stays as "friendly_name" + self.assertEqual(config.to_json(), expected_config_json) + + def test_from_json_with_both_filter_and_friendly_name(self): + data = { + 'task_routing': + { + 'filters': [ + { + 'expression': 'type == "sales"', + 'filter_friendly_name': "Sales", + 'friendly_name': 'Sales2', + 'targets': [ + { + + 'queue': 'WQec62de0e1148b8477f2e24579779c8b1', + 'expression': 'task.language IN worker.languages' + } + ] + }, + { + 'expression': 'type == "marketing"', + 'filter_friendly_name': 'Marketing', + 'friendly_name': 'Marketing2', + 'targets': [ + { + 'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f', + 'expression': 'task.language IN worker.languages' + } + ] + }, + { + 'expression': 'type == "support"', + 'filter_friendly_name': 'Support', + 'friendly_name': 'Support2', + 'targets': [ + { + 'queue': 'WQe5eb317eb23500ade45087ea6522896c', + 'expression': 'task.language IN worker.languages' + } + ] + } + ], + 'default_filter': + { + 'queue': 'WQ05f810d2d130344fd56e3c91ece2e594' + } + } + } + # marshal object + config = WorkflowConfig.json2obj(json.dumps(data)) + self.assertEqual(3, len(config.task_routing.filters)) + self.assertEqual(1, len(config.task_routing.default_filter)) + + # check that the configuration was marshaled to "friendly_name" and not "filter_friendly_name" + expected_config_data = { + "task_routing": + { + "default_filter": + { + "queue": "WQ05f810d2d130344fd56e3c91ece2e594" + }, + "filters": [ + { + "expression": "type == \"sales\"", + "friendly_name": "Sales", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQec62de0e1148b8477f2e24579779c8b1" + } + ] + }, + { + "expression": "type == \"marketing\"", + "friendly_name": "Marketing", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f" + } + ] + }, + { + "expression": "type == \"support\"", + "friendly_name": "Support", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQe5eb317eb23500ade45087ea6522896c" + } + ] + } + ] + } + } + + expected_config_json = json.dumps(expected_config_data, + sort_keys=True, + indent=4) + # check that marshaling back stays as "friendly_name" + self.assertEqual(config.to_json(), expected_config_json) + + def is_json(self, myjson): + try: + json.loads(myjson) + except ValueError as e: + print(e) + return False + return True diff --git a/tests/test_access_token.py b/tests/test_access_token.py new file mode 100644 index 0000000000..44bb96c036 --- /dev/null +++ b/tests/test_access_token.py @@ -0,0 +1,147 @@ +import time +import unittest + +from datetime import datetime +from nose.tools import assert_equal +from twilio.jwt import decode +from twilio.access_token import AccessToken, ConversationsGrant, IpMessagingGrant, VoiceGrant, VideoGrant + +ACCOUNT_SID = 'AC123' +SIGNING_KEY_SID = 'SK123' + + +# python2.6 support +def assert_is_not_none(obj): + assert obj is not None, '%r is None' % obj + + +def assert_in(obj1, obj2): + assert obj1 in obj2, '%r is not in %r' % (obj1, obj2) + + +def assert_greater_equal(obj1, obj2): + assert obj1 > obj2, '%r is not greater than or equal to %r' % (obj1, obj2) + + +class AccessTokenTest(unittest.TestCase): + def _validate_claims(self, payload): + assert_equal(SIGNING_KEY_SID, payload['iss']) + assert_equal(ACCOUNT_SID, payload['sub']) + + assert_is_not_none(payload['exp']) + assert_is_not_none(payload['jti']) + assert_is_not_none(payload['grants']) + + assert_greater_equal(payload['exp'], int(time.time())) + + assert_in(payload['iss'], payload['jti']) + + def test_empty_grants(self): + scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret') + token = str(scat) + + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal({}, payload['grants']) + + def test_nbf(self): + now = int(time.mktime(datetime.now().timetuple())) + scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret', nbf=now) + token = str(scat) + + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal(now, payload['nbf']) + + def test_identity(self): + scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret', identity='test@twilio.com') + token = str(scat) + + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal({ + 'identity': 'test@twilio.com' + }, payload['grants']) + + def test_conversations_grant(self): + scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret') + scat.add_grant(ConversationsGrant(configuration_profile_sid='CP123')) + + token = str(scat) + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal(1, len(payload['grants'])) + assert_equal({ + 'configuration_profile_sid': 'CP123' + }, payload['grants']['rtc']) + + def test_ip_messaging_grant(self): + scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret') + scat.add_grant(IpMessagingGrant(service_sid='IS123', push_credential_sid='CR123')) + + token = str(scat) + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal(1, len(payload['grants'])) + assert_equal({ + 'service_sid': 'IS123', + 'push_credential_sid': 'CR123' + }, payload['grants']['ip_messaging']) + + def test_video_grant(self): + scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret') + scat.add_grant(VideoGrant(configuration_profile_sid='CP123')) + + token = str(scat) + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal(1, len(payload['grants'])) + assert_equal({ + 'configuration_profile_sid': 'CP123' + }, payload['grants']['video']) + + def test_grants(self): + scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret') + scat.add_grant(ConversationsGrant()) + scat.add_grant(IpMessagingGrant()) + scat.add_grant(VideoGrant()) + + token = str(scat) + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal(3, len(payload['grants'])) + assert_equal({}, payload['grants']['rtc']) + assert_equal({}, payload['grants']['ip_messaging']) + assert_equal({}, payload['grants']['video']) + + def test_programmable_voice_grant(self): + grant = VoiceGrant( + outgoing_application_sid='AP123', + outgoing_application_params={ + 'foo': 'bar' + } + ) + + scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret') + scat.add_grant(grant) + + token = str(scat) + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal(1, len(payload['grants'])) + assert_equal({ + 'outgoing': { + 'application_sid': 'AP123', + 'params': { + 'foo': 'bar' + } + } + }, payload['grants']['voice']) diff --git a/tests/test_accounts.py b/tests/test_accounts.py index caacfb5baa..d201312713 100644 --- a/tests/test_accounts.py +++ b/tests/test_accounts.py @@ -18,7 +18,7 @@ def test_usage_records_subresource(self, request): account = Account(mock, 'AC123') account.load_subresources() records = account.usage_records.list() - self.assertEquals(len(records), 2) + self.assertEqual(len(records), 2) @patch("twilio.rest.resources.base.make_twilio_request") def test_usage_triggers_subresource(self, request): @@ -30,4 +30,4 @@ def test_usage_triggers_subresource(self, request): account = Account(mock, 'AC123') account.load_subresources() triggers = account.usage_triggers.list() - self.assertEquals(len(triggers), 2) + self.assertEqual(len(triggers), 2) diff --git a/tests/test_keys.py b/tests/test_keys.py new file mode 100644 index 0000000000..e57ad65ea1 --- /dev/null +++ b/tests/test_keys.py @@ -0,0 +1,75 @@ +from mock import patch, Mock +from twilio.rest.resources.keys import Keys, Key +from tests.tools import create_mock_json + +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +BASE_URL = "https://api.twilio.com/2010-04-01/Accounts/{0}".format(ACCOUNT_SID) +KEY_SID = "SKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Keys(BASE_URL, AUTH) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_get_key(mock): + resp = create_mock_json("tests/resources/keys_instance.json") + mock.return_value = resp + + url = BASE_URL + "/Keys/{0}".format(KEY_SID) + list_resource.get(KEY_SID) + + mock.assert_called_with("GET", url, auth=AUTH, use_json_extension=True) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_create_key(mock): + resp = create_mock_json("tests/resources/keys_instance.json") + resp.status_code = 201 + mock.return_value = resp + + url = BASE_URL + "/Keys" + list_resource.create(friendly_name="Fuzzy Lumpkins' SigningKey") + params = { + 'FriendlyName': "Fuzzy Lumpkins' SigningKey" + } + + mock.assert_called_with("POST", url, data=params, auth=AUTH, use_json_extension=True) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_update_key(mock): + resp = create_mock_json("tests/resources/keys_instance.json") + mock.return_value = resp + + url = BASE_URL + "/Keys/{0}".format(KEY_SID) + list_resource.update(sid=KEY_SID, friendly_name="Fuzzy Lumpkins' SigningKey") + params = { + 'FriendlyName': "Fuzzy Lumpkins' SigningKey" + } + + mock.assert_called_with("POST", url, data=params, auth=AUTH, use_json_extension=True) + + +@patch("twilio.rest.resources.base.Resource.request") +def test_delete_key(mock): + resp = Mock() + resp.content = "" + resp.status_code = 204 + mock.return_value = resp, {} + + key = Key(list_resource, KEY_SID) + key.delete() + + url = BASE_URL + "/Keys/{0}".format(KEY_SID) + mock.assert_called_with("DELETE", url) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_list_keys(mock): + resp = create_mock_json("tests/resources/keys_list.json") + mock.return_value = resp + + url = BASE_URL + "/Keys" + list_resource.list() + + mock.assert_called_with("GET", url, params={}, auth=AUTH, use_json_extension=True) diff --git a/tests/test_phone_numbers.py b/tests/test_phone_numbers.py index 18e88b8caf..6f98b5769b 100644 --- a/tests/test_phone_numbers.py +++ b/tests/test_phone_numbers.py @@ -79,7 +79,7 @@ def test_base_uri(self): entry = json.load(f) resource.load(entry) - self.assertEquals(resource.parent.base_uri, + self.assertEqual(resource.parent.base_uri, ("https://api.twilio.com/2010-04-01/Accounts/AC4bf2dafbed59a573" "3d2c1c1c69a83a28")) diff --git a/tests/test_recordings.py b/tests/test_recordings.py index 04960af112..27993d5cc9 100644 --- a/tests/test_recordings.py +++ b/tests/test_recordings.py @@ -1,6 +1,6 @@ from datetime import date from mock import patch -from nose.tools import raises, assert_equals, assert_true +from nose.tools import raises, assert_equal, assert_true from tests.tools import create_mock_json from twilio.rest.resources import Recordings, Recording @@ -27,6 +27,24 @@ def test_paging(mock): use_json_extension=True) +@patch("twilio.rest.resources.base.make_twilio_request") +def test_paging_iter(mock): + resp = create_mock_json("tests/resources/recordings_list.json") + mock.return_value = resp + + uri = "%s/Recordings" % (BASE_URI) + + next(recordings.iter(before=date(2010, 12, 5))) + exp_params = {'DateCreated<': '2010-12-05'} + mock.assert_called_with("GET", uri, params=exp_params, auth=AUTH, + use_json_extension=True) + + next(recordings.iter(after=date(2012, 12, 7))) + exp_params = {'DateCreated>': '2012-12-07'} + mock.assert_called_with("GET", uri, params=exp_params, auth=AUTH, + use_json_extension=True) + + @patch("twilio.rest.resources.base.make_twilio_request") def test_get(mock): resp = create_mock_json("tests/resources/recordings_instance.json") @@ -39,7 +57,7 @@ def test_get(mock): use_json_extension=True) truri = "%s/Recordings/%s/Transcriptions" % (BASE_URI, RE_SID) - assert_equals(r.transcriptions.uri, truri) + assert_equal(r.transcriptions.uri, truri) @patch("twilio.rest.resources.base.make_twilio_request") diff --git a/tests/test_twiml.py b/tests/test_twiml.py index 7bab2ad1e9..c022baeda0 100644 --- a/tests/test_twiml.py +++ b/tests/test_twiml.py @@ -367,16 +367,16 @@ def setUp(self): # parse twiml XML string with Element Tree and inspect # structure tree = ET.fromstring(xml) - self.conf = tree.find(".//Queue") + self.queue = tree.find(".//Queue") - def test_conf_text(self): - self.assertEqual(self.conf.text.strip(), "TestQueueAttribute") + def test_queue_text(self): + self.assertEqual(self.queue.text.strip(), "TestQueueAttribute") - def test_conf_waiturl(self): - self.assertEqual(self.conf.get('url'), "") + def test_queue_waiturl(self): + self.assertEqual(self.queue.get('url'), "") - def test_conf_method(self): - self.assertEqual(self.conf.get('method'), "GET") + def test_queue_method(self): + self.assertEqual(self.queue.get('method'), "GET") class TestEnqueue(TwilioTest): @@ -390,22 +390,53 @@ def setUp(self): # parse twiml XML string with Element Tree and inspect # structure tree = ET.fromstring(xml) - self.conf = tree.find("./Enqueue") + self.enqueue = tree.find("./Enqueue") - def test_conf_text(self): - self.assertEqual(self.conf.text.strip(), "TestEnqueueAttribute") + def test_enqueue_text(self): + self.assertEqual(self.enqueue.text.strip(), "TestEnqueueAttribute") - def test_conf_waiturl(self): - self.assertEqual(self.conf.get('waitUrl'), "wait") + def test_enqueue_waiturl(self): + self.assertEqual(self.enqueue.get('waitUrl'), "wait") + + def test_enqueue_method(self): + self.assertEqual(self.enqueue.get('method'), "GET") + + def test_enqueue_action(self): + self.assertEqual(self.enqueue.get('action'), "act") + + def test_enqueue_waitmethod(self): + self.assertEqual(self.enqueue.get('waitUrlMethod'), "POST") + + +class TestEnqueueTask(TwilioTest): + + def setUp(self): + r = Response() + with r.enqueue(None, workflowSid="Workflow1") as e: + e.task('{"selected_language":"en"}', priority="10", timeout="50") + + xml = r.toxml() + + # parse twiml XML string with Element Tree and inspect + # structure + tree = ET.fromstring(xml) + self.enqueue = tree.find("./Enqueue") + self.task = self.enqueue.find(".//Task") + + def test_found_task(self): + self.assertNotEqual(None, self.task) + + def test_enqueue_workflow_sid(self): + self.assertEqual(self.enqueue.get('workflowSid'), "Workflow1") - def test_conf_method(self): - self.assertEqual(self.conf.get('method'), "GET") + def test_enqueue_task_attributes(self): + self.assertEqual(self.task.text.strip(), '{"selected_language":"en"}') - def test_conf_action(self): - self.assertEqual(self.conf.get('action'), "act") + def test_enqueue_task_priority(self): + self.assertEqual(self.task.get('priority'), "10") - def test_conf_waitmethod(self): - self.assertEqual(self.conf.get('waitUrlMethod'), "POST") + def test_enqueue_task_timeout(self): + self.assertEqual(self.task.get('timeout'), "50") class TestDial(TwilioTest): diff --git a/tests/trunking/test_credential_lists.py b/tests/trunking/test_credential_lists.py new file mode 100644 index 0000000000..f5b9594601 --- /dev/null +++ b/tests/trunking/test_credential_lists.py @@ -0,0 +1,101 @@ +import unittest +from mock import Mock, patch +from nose.tools import assert_equal, assert_true +from tests.tools import create_mock_json +from twilio.rest.resources.trunking.credential_lists import CredentialLists + +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "auth_token") +BASE_URI = "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111" + + +class CredentialListsTest(unittest.TestCase): + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_credential_lists(self, request): + resp = create_mock_json('tests/resources/trunking/credential_lists_list.json') + resp.status_code = 200 + request.return_value = resp + + credential_lists = CredentialLists(BASE_URI, AUTH) + result = credential_lists.list() + + assert_equal(len(result), 1) + assert_equal(result[0].sid, 'CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result[0].friendly_name, "Test list") + assert_equal(result[0].url, "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/CredentialLists".format(BASE_URI), + auth=AUTH, + params={}, + use_json_extension=False, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_credential_lists_instance(self, request): + resp = create_mock_json('tests/resources/trunking/credential_lists_instance.json') + resp.status_code = 200 + request.return_value = resp + + credential_lists = CredentialLists(BASE_URI, AUTH) + result = credential_lists.get('CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_equal(result.sid, "CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Test") + assert_equal(result.url, "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_create_credential_lists_instance(self, request): + resp = create_mock_json('tests/resources/trunking/credential_lists_instance.json') + resp.status_code = 201 + request.return_value = resp + + credential_lists = CredentialLists(BASE_URI, AUTH) + result = credential_lists.create('CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_equal(result.sid, "CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Test") + assert_equal(result.url, "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + data_dict = dict() + data_dict['CredentialListSid'] = 'CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + request.assert_called_with( + "POST", + "{0}/CredentialLists".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + data=data_dict, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_delete_credential_lists_instance(self, request): + resp = Mock() + resp.status_code = 204 + request.return_value = resp + + credential_lists = CredentialLists(BASE_URI, AUTH) + result = credential_lists.delete('CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_true(result) + + request.assert_called_with( + "DELETE", + "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) diff --git a/tests/trunking/test_ip_access_control_lists.py b/tests/trunking/test_ip_access_control_lists.py new file mode 100644 index 0000000000..59dfa9f5f3 --- /dev/null +++ b/tests/trunking/test_ip_access_control_lists.py @@ -0,0 +1,103 @@ +import unittest +from mock import Mock, patch +from nose.tools import assert_equal, assert_true +from tests.tools import create_mock_json +from twilio.rest.resources.trunking.ip_access_control_lists import ( + IpAccessControlLists +) + +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "auth_token") +BASE_URI = "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111" + + +class IpAccessControlListsTest(unittest.TestCase): + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_ip_access_control_lists(self, request): + resp = create_mock_json('tests/resources/trunking/ip_access_control_lists_list.json') + resp.status_code = 200 + request.return_value = resp + + ip_access_control_lists = IpAccessControlLists(BASE_URI, AUTH) + result = ip_access_control_lists.list() + + assert_equal(len(result), 1) + assert_equal(result[0].sid, 'ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result[0].friendly_name, "Test") + assert_equal(result[0].url, "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/IpAccessControlLists".format(BASE_URI), + auth=AUTH, + params={}, + use_json_extension=False, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_ip_access_control_lists_instance(self, request): + resp = create_mock_json('tests/resources/trunking/ip_access_control_lists_instance.json') + resp.status_code = 200 + request.return_value = resp + + ip_access_control_lists = IpAccessControlLists(BASE_URI, AUTH) + result = ip_access_control_lists.get('ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_equal(result.sid, "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Test") + assert_equal(result.url, "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_associate_ip_access_control_lists_instance(self, request): + resp = create_mock_json('tests/resources/trunking/ip_access_control_lists_instance.json') + resp.status_code = 201 + request.return_value = resp + + ip_access_control_lists = IpAccessControlLists(BASE_URI, AUTH) + result = ip_access_control_lists.create('ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_equal(result.sid, "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Test") + assert_equal(result.url, "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + data_dict = dict() + data_dict['IpAccessControlListSid'] = 'ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + request.assert_called_with( + "POST", + "{0}/IpAccessControlLists".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + data=data_dict, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_disassociate_ip_access_control_lists_instance(self, request): + resp = Mock() + resp.status_code = 204 + request.return_value = resp + + ip_access_control_lists = IpAccessControlLists(BASE_URI, AUTH) + result = ip_access_control_lists.delete('ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_true(result) + + request.assert_called_with( + "DELETE", + "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) diff --git a/tests/trunking/test_origination_urls.py b/tests/trunking/test_origination_urls.py new file mode 100644 index 0000000000..3da34feb30 --- /dev/null +++ b/tests/trunking/test_origination_urls.py @@ -0,0 +1,150 @@ +import unittest +from mock import Mock, patch +from nose.tools import assert_equal, assert_true +from tests.tools import create_mock_json +from twilio.rest.resources.trunking.origination_urls import ( + OriginationUrls +) + +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "auth_token") +BASE_URI = "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111" + + +class OriginationUrlsTest(unittest.TestCase): + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_origination_urls_lists(self, request): + resp = create_mock_json('tests/resources/trunking/origination_urls_list.json') + resp.status_code = 200 + request.return_value = resp + + origination_urls = OriginationUrls(BASE_URI, AUTH) + result = origination_urls.list() + + assert_equal(len(result), 1) + assert_equal(result[0].sid, 'OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result[0].friendly_name, "Name") + assert_equal(result[0].sip_url, "sip:169.10.1.35") + assert_equal(result[0].weight, 10) + assert_equal(result[0].priority, 20) + assert_true(result[0].enabled) + assert_equal(result[0].url, "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/OriginationUrls".format(BASE_URI), + auth=AUTH, + params={}, + use_json_extension=False, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_origination_urls_instance(self, request): + resp = create_mock_json('tests/resources/trunking/origination_urls_instance.json') + resp.status_code = 200 + request.return_value = resp + + origination_urls = OriginationUrls(BASE_URI, AUTH) + result = origination_urls.get('OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_equal(result.sid, "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Name") + assert_equal(result.sip_url, "sip:169.10.1.35") + assert_equal(result.weight, 10) + assert_equal(result.priority, 20) + assert_true(result.enabled) + assert_equal(result.url, "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_create_origination_urls_instance(self, request): + resp = create_mock_json('tests/resources/trunking/origination_urls_instance.json') + resp.status_code = 201 + request.return_value = resp + + origination_urls = OriginationUrls(BASE_URI, AUTH) + result = origination_urls.create('Name', 'sip:169.10.1.35') + + assert_equal(result.sid, "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Name") + assert_equal(result.sip_url, "sip:169.10.1.35") + assert_equal(result.weight, 10) + assert_equal(result.priority, 20) + assert_true(result.enabled) + assert_equal(result.url, "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + data_dict = dict() + data_dict['FriendlyName'] = 'Name' + data_dict['SipUrl'] = 'sip:169.10.1.35' + data_dict['Priority'] = 10 + data_dict['Weight'] = 10 + data_dict['Enabled'] = 'true' + + request.assert_called_with( + "POST", + "{0}/OriginationUrls".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + data=data_dict, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_update_origination_urls_instance(self, request): + resp = create_mock_json('tests/resources/trunking/origination_urls_instance.json') + resp.status_code = 200 + request.return_value = resp + + origination_urls = OriginationUrls(BASE_URI, AUTH) + result = origination_urls.update('OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', {'Priority': 10}) + + assert_equal(result.sid, "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Name") + assert_equal(result.sip_url, "sip:169.10.1.35") + assert_equal(result.weight, 10) + assert_equal(result.priority, 20) + assert_true(result.enabled) + assert_equal(result.url, "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + data_dict = dict() + data_dict['Priority'] = 10 + + request.assert_called_with( + "POST", + "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + data=data_dict + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_delete_origination_urls_instance(self, request): + resp = Mock() + resp.status_code = 204 + request.return_value = resp + + origination_urls = OriginationUrls(BASE_URI, AUTH) + result = origination_urls.delete('OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_true(result) + + request.assert_called_with( + "DELETE", + "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) diff --git a/tests/trunking/test_phone_numbers.py b/tests/trunking/test_phone_numbers.py new file mode 100644 index 0000000000..8ed54ab33a --- /dev/null +++ b/tests/trunking/test_phone_numbers.py @@ -0,0 +1,158 @@ +import unittest +from mock import Mock, patch +from nose.tools import assert_equal, assert_true +from tests.tools import create_mock_json +from twilio.rest.resources.trunking.phone_numbers import ( + PhoneNumbers +) + +API_BASE_URI = "https://api.twilio.com/2010-04-01/Accounts" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +PHONE_NUMBERS_BASE_URI = "{0}/{1}/{2}".format(API_BASE_URI, ACCOUNT_SID, + "IncomingPhoneNumbers") +AUTH = (ACCOUNT_SID, "auth_token") +BASE_URI = "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111" + + +class PhoneNumbersTest(unittest.TestCase): + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_phone_numbers_lists(self, request): + resp = create_mock_json( + 'tests/resources/trunking/phone_numbers_list.json') + resp.status_code = 200 + request.return_value = resp + + phone_numbers = PhoneNumbers(BASE_URI, AUTH) + result = phone_numbers.list() + + assert_equal(len(result), 1) + assert_equal(result[0].sid, 'PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].account_sid, + 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result[0].friendly_name, "Name") + assert_equal(result[0].phone_number, "+14158675309") + assert_equal(result[0].api_version, "2010-04-01") + assert_equal(result[0].voice_caller_id_lookup, False) + assert_equal(result[0].voice_fallback_method, "POST") + assert_equal(result[0].status_callback_method, "POST") + assert_equal(result[0].sms_url, + "https://demo.twilio.com/welcome/sms/reply/") + assert_equal(result[0].sms_method, "POST") + assert_equal(result[0].sms_fallback_method, "POST") + assert_equal(result[0].address_requirements, "none") + assert_equal(result[0].beta, False) + assert_equal(result[0].url, + "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa". + format(BASE_URI)) + assert_equal(result[0].links['phone_number'], + "{0}/{1}".format(PHONE_NUMBERS_BASE_URI, + "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) + request.assert_called_with( + "GET", + "{0}/PhoneNumbers".format(BASE_URI), + auth=AUTH, + params={}, + use_json_extension=False, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_phone_numbers_instance(self, request): + resp = create_mock_json( + 'tests/resources/trunking/phone_numbers_instance.json') + resp.status_code = 200 + request.return_value = resp + + phone_numbers = PhoneNumbers(BASE_URI, AUTH) + result = phone_numbers.get('PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_equal(result.sid, 'PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Name") + assert_equal(result.phone_number, "+14158675309") + assert_equal(result.api_version, "2010-04-01") + assert_equal(result.voice_caller_id_lookup, False) + assert_equal(result.voice_fallback_method, "POST") + assert_equal(result.status_callback_method, "POST") + assert_equal(result.sms_url, + "https://demo.twilio.com/welcome/sms/reply/") + assert_equal(result.sms_method, "POST") + assert_equal(result.sms_fallback_method, "POST") + assert_equal(result.address_requirements, "none") + assert_equal(result.beta, False) + assert_equal(result.url, + "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format( + BASE_URI)) + assert_equal(result.links['phone_number'], + "{0}/{1}".format(PHONE_NUMBERS_BASE_URI, + "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) + + request.assert_called_with( + "GET", + "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format( + BASE_URI), + auth=AUTH, + use_json_extension=False + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_associate_phone_numbers_instance(self, request): + resp = create_mock_json( + 'tests/resources/trunking/phone_numbers_instance.json') + resp.status_code = 201 + request.return_value = resp + + phone_numbers = PhoneNumbers(BASE_URI, AUTH) + result = phone_numbers.create('PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_equal(result.sid, 'PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Name") + assert_equal(result.phone_number, "+14158675309") + assert_equal(result.api_version, "2010-04-01") + assert_equal(result.voice_caller_id_lookup, False) + assert_equal(result.voice_fallback_method, "POST") + assert_equal(result.status_callback_method, "POST") + assert_equal(result.sms_url, + "https://demo.twilio.com/welcome/sms/reply/") + assert_equal(result.sms_method, "POST") + assert_equal(result.sms_fallback_method, "POST") + assert_equal(result.address_requirements, "none") + assert_equal(result.beta, False) + assert_equal(result.url, + "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format( + BASE_URI)) + assert_equal(result.links['phone_number'], + "{0}/{1}".format(PHONE_NUMBERS_BASE_URI, + "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) + + data_dict = dict() + data_dict['PhoneNumberSid'] = 'PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + request.assert_called_with( + "POST", + "{0}/PhoneNumbers".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + data=data_dict, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_disassociate_phone_numbers_instance(self, request): + resp = Mock() + resp.status_code = 204 + request.return_value = resp + + phone_numbers = PhoneNumbers(BASE_URI, AUTH) + result = phone_numbers.delete('PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_true(result) + + request.assert_called_with( + "DELETE", + "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format( + BASE_URI), + auth=AUTH, + use_json_extension=False + ) diff --git a/tests/trunking/test_trunks.py b/tests/trunking/test_trunks.py new file mode 100644 index 0000000000..6ce82d191e --- /dev/null +++ b/tests/trunking/test_trunks.py @@ -0,0 +1,208 @@ +import unittest +from mock import Mock, patch +from nose.tools import assert_equal, assert_true +from tests.tools import create_mock_json +from twilio.rest.resources.trunking.trunks import ( + Trunks +) + +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "auth_token") +BASE_URI = "https://trunking.twilio.com/v1" +TRUNK_SID = "TK11111111111111111111111111111111" + + +class TrunksTest(unittest.TestCase): + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_trunks_lists(self, request): + resp = create_mock_json('tests/resources/trunking/trunks_list.json') + resp.status_code = 200 + request.return_value = resp + + trunks = Trunks(BASE_URI, AUTH) + result = trunks.list() + + assert_equal(len(result), 1) + assert_equal(result[0].sid, 'TK11111111111111111111111111111111') + assert_equal(result[0].account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].domain_name, "test-trunk.pstn.twilio.com") + assert_equal(result[0].friendly_name, "Test") + assert_equal(result[0].recording, + {"trim": "do-not-trim", + "mode": "record-from-ringing"}) + + assert_equal(result[0].auth_type, "CREDENTIAL_LIST") + assert_equal(result[0].auth_type_set, ["CREDENTIAL_LIST"]) + TRUNK_INSTANCE_BASE_URI = "{0}/{1}/{2}".format(BASE_URI, "Trunks", + TRUNK_SID) + + assert_equal(result[0].url, TRUNK_INSTANCE_BASE_URI) + + assert_equal(result[0].links['origination_urls'], + "{0}/OriginationUrls".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result[0].links['credential_lists'], + "{0}/CredentialLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result[0].links['ip_access_control_lists'], + "{0}/IpAccessControlLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result[0].links['phone_numbers'], + "{0}/PhoneNumbers".format(TRUNK_INSTANCE_BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/Trunks".format(BASE_URI), + auth=AUTH, + params={}, + use_json_extension=False, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_trunks_instance(self, request): + resp = create_mock_json('tests/resources/trunking/trunks_instance.json') + resp.status_code = 200 + request.return_value = resp + + trunks = Trunks(BASE_URI, AUTH) + result = trunks.get('TK11111111111111111111111111111111') + + assert_equal(result.sid, 'TK11111111111111111111111111111111') + assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result.domain_name, "test-trunk.pstn.twilio.com") + assert_equal(result.friendly_name, "Test") + assert_equal(result.recording, + {"trim": "do-not-trim", + "mode": "record-from-ringing"}) + + assert_equal(result.auth_type, "CREDENTIAL_LIST") + assert_equal(result.auth_type_set, ["CREDENTIAL_LIST"]) + TRUNK_INSTANCE_BASE_URI = "{0}/{1}/{2}".format(BASE_URI, "Trunks", + TRUNK_SID) + + assert_equal(result.url, TRUNK_INSTANCE_BASE_URI) + + assert_equal(result.links['origination_urls'], + "{0}/OriginationUrls".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['credential_lists'], + "{0}/CredentialLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['ip_access_control_lists'], + "{0}/IpAccessControlLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['phone_numbers'], + "{0}/PhoneNumbers".format(TRUNK_INSTANCE_BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/Trunks/TK11111111111111111111111111111111".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_create_trunk_instance(self, request): + resp = create_mock_json('tests/resources/trunking/trunks_instance.json') + resp.status_code = 201 + request.return_value = resp + + trunks = Trunks(BASE_URI, AUTH) + kwargs = { + 'FriendlyName': 'Test', + 'DomainName': 'test-trunk.pstn.twilio.com' + } + result = trunks.create(**kwargs) + + assert_equal(result.sid, 'TK11111111111111111111111111111111') + assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result.domain_name, "test-trunk.pstn.twilio.com") + assert_equal(result.friendly_name, "Test") + assert_equal(result.recording, + {"trim": "do-not-trim", + "mode": "record-from-ringing"}) + + assert_equal(result.auth_type, "CREDENTIAL_LIST") + assert_equal(result.auth_type_set, ["CREDENTIAL_LIST"]) + TRUNK_INSTANCE_BASE_URI = "{0}/{1}/{2}".format(BASE_URI, "Trunks", + TRUNK_SID) + + assert_equal(result.url, TRUNK_INSTANCE_BASE_URI) + + assert_equal(result.links['origination_urls'], + "{0}/OriginationUrls".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['credential_lists'], + "{0}/CredentialLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['ip_access_control_lists'], + "{0}/IpAccessControlLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['phone_numbers'], + "{0}/PhoneNumbers".format(TRUNK_INSTANCE_BASE_URI)) + + data_dict = dict() + data_dict['FriendlyName'] = 'Test' + data_dict['DomainName'] = 'test-trunk.pstn.twilio.com' + + request.assert_called_with( + "POST", + "{0}/Trunks".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + data=data_dict, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_update_trunk_instance(self, request): + resp = create_mock_json('tests/resources/trunking/trunks_instance.json') + resp.status_code = 200 + request.return_value = resp + + trunks = Trunks(BASE_URI, AUTH) + result = trunks.update('TK11111111111111111111111111111111', {'FriendlyName': 'Test'}) + + assert_equal(result.sid, 'TK11111111111111111111111111111111') + assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result.domain_name, "test-trunk.pstn.twilio.com") + assert_equal(result.friendly_name, "Test") + assert_equal(result.recording, + {"trim": "do-not-trim", + "mode": "record-from-ringing"}) + + assert_equal(result.auth_type, "CREDENTIAL_LIST") + assert_equal(result.auth_type_set, ["CREDENTIAL_LIST"]) + TRUNK_INSTANCE_BASE_URI = "{0}/{1}/{2}".format(BASE_URI, "Trunks", + TRUNK_SID) + + assert_equal(result.url, TRUNK_INSTANCE_BASE_URI) + + assert_equal(result.links['origination_urls'], + "{0}/OriginationUrls".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['credential_lists'], + "{0}/CredentialLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['ip_access_control_lists'], + "{0}/IpAccessControlLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['phone_numbers'], + "{0}/PhoneNumbers".format(TRUNK_INSTANCE_BASE_URI)) + + data_dict = dict() + data_dict['FriendlyName'] = 'Test' + + request.assert_called_with( + "POST", + "{0}/Trunks/TK11111111111111111111111111111111".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + data=data_dict + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_delete_trunk_instance(self, request): + resp = Mock() + resp.status_code = 204 + request.return_value = resp + + trunks = Trunks(BASE_URI, AUTH) + result = trunks.delete('TK11111111111111111111111111111111') + + assert_true(result) + + request.assert_called_with( + "DELETE", + "{0}/Trunks/TK11111111111111111111111111111111".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) diff --git a/tox.ini b/tox.ini index 5249597112..51d8d59264 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26, py27, py32, py33, py34, pypy +envlist = py26, py27, py32, py33, py34, py35, pypy [testenv] deps= -r{toxinidir}/tests/requirements.txt diff --git a/twilio/access_token.py b/twilio/access_token.py new file mode 100644 index 0000000000..9621c041ad --- /dev/null +++ b/twilio/access_token.py @@ -0,0 +1,150 @@ +import time +from twilio import jwt + + +class IpMessagingGrant(object): + """ Grant to access Twilio IP Messaging """ + def __init__(self, service_sid=None, endpoint_id=None, + deployment_role_sid=None, push_credential_sid=None): + self.service_sid = service_sid + self.endpoint_id = endpoint_id + self.deployment_role_sid = deployment_role_sid + self.push_credential_sid = push_credential_sid + + @property + def key(self): + return "ip_messaging" + + def to_payload(self): + grant = {} + if self.service_sid: + grant['service_sid'] = self.service_sid + if self.endpoint_id: + grant['endpoint_id'] = self.endpoint_id + if self.deployment_role_sid: + grant['deployment_role_sid'] = self.deployment_role_sid + if self.push_credential_sid: + grant['push_credential_sid'] = self.push_credential_sid + + return grant + + +class ConversationsGrant(object): + """ Grant to access Twilio Conversations """ + def __init__(self, configuration_profile_sid=None): + self.configuration_profile_sid = configuration_profile_sid + + @property + def key(self): + return "rtc" + + def to_payload(self): + grant = {} + if self.configuration_profile_sid: + grant['configuration_profile_sid'] = self.configuration_profile_sid + + return grant + + +class VoiceGrant(object): + """ Grant to access Twilio Programmable Voice""" + def __init__(self, + outgoing_application_sid=None, + outgoing_application_params=None, + push_credential_sid=None, + endpoint_id=None): + self.outgoing_application_sid = outgoing_application_sid + """ :type : str """ + self.outgoing_application_params = outgoing_application_params + """ :type : dict """ + self.push_credential_sid = push_credential_sid + """ :type : str """ + self.endpoint_id = endpoint_id + """ :type : str """ + + @property + def key(self): + return "voice" + + def to_payload(self): + grant = {} + if self.outgoing_application_sid: + grant['outgoing'] = {} + grant['outgoing']['application_sid'] = \ + self.outgoing_application_sid + + if self.outgoing_application_params: + grant['outgoing']['params'] = self.outgoing_application_params + + if self.push_credential_sid: + grant['push_credential_sid'] = self.push_credential_sid + + if self.endpoint_id: + grant['endpoint_id'] = self.endpoint_id + + return grant + + +class VideoGrant(object): + """ Grant to access Twilio Video """ + def __init__(self, configuration_profile_sid=None): + self.configuration_profile_sid = configuration_profile_sid + + @property + def key(self): + return "video" + + def to_payload(self): + grant = {} + if self.configuration_profile_sid: + grant['configuration_profile_sid'] = self.configuration_profile_sid + + return grant + + +class AccessToken(object): + """ Access Token used to access Twilio Resources """ + def __init__(self, account_sid, signing_key_sid, secret, + identity=None, ttl=3600, nbf=None): + self.account_sid = account_sid + self.signing_key_sid = signing_key_sid + self.secret = secret + + self.identity = identity + self.ttl = ttl + self.nbf = nbf + self.grants = [] + + def add_grant(self, grant): + self.grants.append(grant) + + def to_jwt(self, algorithm='HS256'): + now = int(time.time()) + headers = { + "typ": "JWT", + "cty": "twilio-fpa;v=1" + } + + grants = {} + if self.identity: + grants["identity"] = self.identity + + for grant in self.grants: + grants[grant.key] = grant.to_payload() + + payload = { + "jti": '{0}-{1}'.format(self.signing_key_sid, now), + "iss": self.signing_key_sid, + "sub": self.account_sid, + "exp": now + self.ttl, + "grants": grants + } + + if self.nbf is not None: + payload['nbf'] = self.nbf + + return jwt.encode(payload, self.secret, headers=headers, + algorithm=algorithm) + + def __str__(self): + return self.to_jwt() diff --git a/twilio/jwt/__init__.py b/twilio/jwt/__init__.py index edb4062433..93f6b60a34 100644 --- a/twilio/jwt/__init__.py +++ b/twilio/jwt/__init__.py @@ -41,9 +41,11 @@ def base64url_encode(input): return base64.urlsafe_b64encode(input).decode('utf-8').replace('=', '') -def encode(payload, key, algorithm='HS256'): +def encode(payload, key, algorithm='HS256', headers=None): segments = [] header = {"typ": "JWT", "alg": algorithm} + if headers: + header.update(headers) segments.append(base64url_encode(binary(json.dumps(header)))) segments.append(base64url_encode(binary(json.dumps(payload)))) sign_input = '.'.join(segments) diff --git a/twilio/rest/__init__.py b/twilio/rest/__init__.py index 31c6338fea..f4077334aa 100644 --- a/twilio/rest/__init__.py +++ b/twilio/rest/__init__.py @@ -1,8 +1,11 @@ from .base import set_twilio_proxy from .client import TwilioRestClient +from .ip_messaging import TwilioIpMessagingClient from .lookups import TwilioLookupsClient from .pricing import TwilioPricingClient from .task_router import TwilioTaskRouterClient +from .trunking import TwilioTrunkingClient -_hush_pyflakes = [set_twilio_proxy, TwilioRestClient, TwilioLookupsClient, - TwilioPricingClient, TwilioTaskRouterClient] +_hush_pyflakes = [set_twilio_proxy, TwilioRestClient, TwilioIpMessagingClient, + TwilioLookupsClient, TwilioPricingClient, + TwilioTaskRouterClient, TwilioTrunkingClient] diff --git a/twilio/rest/base.py b/twilio/rest/base.py index 050321c59c..7d7a2d9702 100644 --- a/twilio/rest/base.py +++ b/twilio/rest/base.py @@ -30,8 +30,8 @@ def set_twilio_proxy(proxy_url, proxy_port): class TwilioClient(object): def __init__(self, account=None, token=None, base="https://api.twilio.com", - version="2010-04-01", - timeout=UNSET_TIMEOUT): + version="2010-04-01", timeout=UNSET_TIMEOUT, + request_account=None): """ Create a Twilio API client. """ @@ -58,8 +58,9 @@ def __init__(self, account=None, token=None, base="https://api.twilio.com", self.base = base self.auth = (account, token) self.timeout = timeout + req_account = request_account if request_account else account self.account_uri = "{0}/{1}/Accounts/{2}".format(base, - version, account) + version, req_account) def request(self, path, method=None, vars=None): """sends a request and gets a response from the Twilio REST API diff --git a/twilio/rest/client.py b/twilio/rest/client.py index 9537cf41bf..ed89641e30 100644 --- a/twilio/rest/client.py +++ b/twilio/rest/client.py @@ -2,6 +2,7 @@ from twilio.rest.resources import ( UNSET_TIMEOUT, Accounts, + Addresses, Applications, AuthorizedConnectApps, CallFeedback, @@ -11,6 +12,7 @@ Conferences, ConnectApps, DependentPhoneNumbers, + Keys, MediaList, Members, Messages, @@ -40,12 +42,13 @@ class TwilioRestClient(TwilioClient): """ def __init__(self, account=None, token=None, base="https://api.twilio.com", - version="2010-04-01", timeout=UNSET_TIMEOUT): + version="2010-04-01", timeout=UNSET_TIMEOUT, + request_account=None): """ Create a Twilio REST API client. """ super(TwilioRestClient, self).__init__(account, token, base, version, - timeout) + timeout, request_account) version_uri = "%s/%s" % (base, version) @@ -56,6 +59,7 @@ def __init__(self, account=None, token=None, base="https://api.twilio.com", self.auth, timeout ) + self.addresses = Addresses(self.account_uri, self.auth, timeout) self.calls = Calls(self.account_uri, self.auth, timeout) self.caller_ids = CallerIds(self.account_uri, self.auth, timeout) self.connect_apps = ConnectApps(self.account_uri, self.auth, timeout) @@ -74,6 +78,7 @@ def __init__(self, account=None, token=None, base="https://api.twilio.com", self.media = MediaList(self.account_uri, self.auth, timeout) self.sip = Sip(self.account_uri, self.auth, timeout) self.tokens = Tokens(self.account_uri, self.auth, timeout) + self.keys = Keys(self.account_uri, self.auth, timeout) def participants(self, conference_sid): """ diff --git a/twilio/rest/ip_messaging.py b/twilio/rest/ip_messaging.py new file mode 100644 index 0000000000..033864c801 --- /dev/null +++ b/twilio/rest/ip_messaging.py @@ -0,0 +1,32 @@ +from twilio.rest.base import TwilioClient +from twilio.rest.resources import UNSET_TIMEOUT +from twilio.rest.resources.ip_messaging.services import Services +from twilio.rest.resources.ip_messaging.credentials import Credentials + + +class TwilioIpMessagingClient(TwilioClient): + """ + A client for accessing the Twilio IP Messaging API. + + The Twilio IP Messaging API provides information about events. For more + information, see the + `IP Messaging API documentation `_. + + :param str account: Your Account Sid from `your dashboard + `_ + :param str token: Your Auth Token from `your dashboard + `_ + :param float timeout: The socket and read timeout for requests to Twilio + """ + + def __init__(self, account=None, token=None, + base="https://ip-messaging.twilio.com", version="v1", + timeout=UNSET_TIMEOUT, request_account=None): + + super(TwilioIpMessagingClient, self).__init__(account, token, base, + version, timeout, + request_account) + + self.version_uri = "%s/%s" % (base, version) + self.services = Services(self.version_uri, self.auth, timeout) + self.credentials = Credentials(self.version_uri, self.auth, timeout) diff --git a/twilio/rest/lookups.py b/twilio/rest/lookups.py index b71e30da55..8d591825c3 100644 --- a/twilio/rest/lookups.py +++ b/twilio/rest/lookups.py @@ -20,10 +20,11 @@ class TwilioLookupsClient(TwilioClient): def __init__(self, account=None, token=None, base="https://lookups.twilio.com", version="v1", - timeout=UNSET_TIMEOUT): + timeout=UNSET_TIMEOUT, request_account=None): super(TwilioLookupsClient, self).__init__(account, token, base, - version, timeout) + version, timeout, + request_account) self.version_uri = "%s/%s" % (base, version) self.phone_numbers = PhoneNumbers(self.version_uri, self.auth, timeout) diff --git a/twilio/rest/monitor.py b/twilio/rest/monitor.py index 980e2f3c0b..9554a844ff 100644 --- a/twilio/rest/monitor.py +++ b/twilio/rest/monitor.py @@ -21,10 +21,11 @@ class TwilioMonitorClient(TwilioClient): def __init__(self, account=None, token=None, base="https://monitor.twilio.com", version="v1", - timeout=UNSET_TIMEOUT): + timeout=UNSET_TIMEOUT, request_account=None): super(TwilioMonitorClient, self).__init__(account, token, base, - version, timeout) + version, timeout, + request_account) self.version_uri = "%s/%s" % (base, version) self.events = Events(self.version_uri, self.auth, timeout) diff --git a/twilio/rest/pricing.py b/twilio/rest/pricing.py index 998c711ac1..61e7a84058 100644 --- a/twilio/rest/pricing.py +++ b/twilio/rest/pricing.py @@ -3,6 +3,7 @@ from twilio.rest.resources.pricing import ( PhoneNumbers, Voice, + MessagingCountries, ) @@ -20,11 +21,23 @@ class TwilioPricingClient(TwilioClient): def __init__(self, account=None, token=None, base="https://pricing.twilio.com", version="v1", - timeout=UNSET_TIMEOUT): + timeout=UNSET_TIMEOUT, request_account=None): super(TwilioPricingClient, self).__init__(account, token, base, - version, timeout) + version, timeout, + request_account) - uri_base = "{}/{}".format(base, version) + self.uri_base = "{}/{}".format(base, version) - self.voice = Voice(uri_base, self.auth, self.timeout) - self.phone_numbers = PhoneNumbers(uri_base, self.auth, self.timeout) + self.voice = Voice(self.uri_base, self.auth, self.timeout) + self.phone_numbers = PhoneNumbers(self.uri_base, self.auth, + self.timeout) + + def messaging_countries(self): + """ + Returns a :class:`MessagingCountries` resource + :return: MessagingCountries + """ + messaging_countries_uri = "{0}/Messaging".format( + self.uri_base) + return MessagingCountries(messaging_countries_uri, self.auth, + self.timeout) diff --git a/twilio/rest/resources/__init__.py b/twilio/rest/resources/__init__.py index fa9aef58ec..7e74cad645 100644 --- a/twilio/rest/resources/__init__.py +++ b/twilio/rest/resources/__init__.py @@ -73,3 +73,16 @@ DependentPhoneNumber, DependentPhoneNumbers, ) + +from .trunking import ( + CredentialList, + CredentialLists, + IpAccessControlList, + IpAccessControlLists, + OriginationUrl, + OriginationUrls, + Trunk, + Trunks, +) + +from .keys import Key, Keys diff --git a/twilio/rest/resources/accounts.py b/twilio/rest/resources/accounts.py index d54b9d4067..78c2f65370 100644 --- a/twilio/rest/resources/accounts.py +++ b/twilio/rest/resources/accounts.py @@ -9,6 +9,7 @@ from .conferences import Conferences from .connect_apps import ConnectApps, AuthorizedConnectApps from .queues import Queues +from .keys import Keys from .usage import UsageRecords, UsageTriggers from .messages import Messages from .media import MediaList @@ -42,6 +43,7 @@ class Account(InstanceResource): MediaList, Messages, Sip, + Keys, ] def update(self, **kwargs): diff --git a/twilio/rest/resources/ip_messaging/__init__.py b/twilio/rest/resources/ip_messaging/__init__.py new file mode 100644 index 0000000000..55f60ad203 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/__init__.py @@ -0,0 +1,34 @@ +from .services import ( + Service, + Services +) + +from .channels import ( + Channel, + Channels +) + +from .members import ( + Member, + Members +) + +from .messages import ( + Message, + Messages +) + +from .roles import ( + Role, + Roles +) + +from .users import ( + User, + Users +) + +from .credentials import ( + Credential, + Credentials +) diff --git a/twilio/rest/resources/ip_messaging/channels.py b/twilio/rest/resources/ip_messaging/channels.py new file mode 100644 index 0000000000..b7e4f5529a --- /dev/null +++ b/twilio/rest/resources/ip_messaging/channels.py @@ -0,0 +1,78 @@ +from .members import Members +from .messages import Messages +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Channel(NextGenInstanceResource): + + subresources = [ + Members, + Messages + ] + + def update(self, **kwargs): + """ + Updates the Channel instance + :param sid: Channel instance identifier + :param type: Channel type + :param friendly_name: Channel's friendly name + :param unique_name: Channel's Unique name + :param attributes: Additional attributes that needs to be stored with + channel + :return: the updated instance + """ + return self.update_instance(**kwargs) + + def delete(self): + """ + Delete this channel + """ + return self.delete_instance() + + +class Channels(NextGenListResource): + + name = "Channels" + instance = Channel + + def list(self, **kwargs): + """ + Returns a page of :class:`Channel` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Channel instance resource representation. + """ + return self.get_instances(kwargs) + + def create(self, **kwargs): + """ + Create a channel. + + :param str friendly_name: Channel's friendly name + :param unique_name: Channel's Unique name + :param str attributes: Developer-specific data (json) storage + + :return: A :class:`Channel` object + """ + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Channel + """ + return self.delete_instance(sid) + + def update(self, sid, **kwargs): + """ + Updates the Channel instance identified by sid + :param sid: Channel instance identifier + :param type: Channel type + :param friendly_name: Channel's friendly name + :param unique_name: Channel's Unique name + :param attributes: Additional attributes that needs to be stored with + channel + :return: Updated instance + """ + return self.update_instance(sid, kwargs) diff --git a/twilio/rest/resources/ip_messaging/credentials.py b/twilio/rest/resources/ip_messaging/credentials.py new file mode 100644 index 0000000000..3b84f85364 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/credentials.py @@ -0,0 +1,80 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Credential(NextGenInstanceResource): + + def update(self, credential_type, **kwargs): + """ + Updates this Credential instance + :param sid: Credential instance identifier + :param credential_type: Credential type + :param friendly_name: Credential's friendly name + :param certificate: Credential's certificate + :param private_key: Credential's Private key + :param sandbox: Credential's Sandbox + :param api_key: Credential's Api Key + :return: Updated instance + """ + kwargs['type'] = credential_type + return self.update_instance(**kwargs) + + def delete(self): + """ + Delete this credential + """ + return self.delete_instance() + + +class Credentials(NextGenListResource): + + name = "Credentials" + instance = Credential + + def list(self, **kwargs): + """ + Returns a page of :class:`Credential` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Credential instance resource representation. + + :param date after: Only list alerts logged after this datetime + :param date before: Only list alerts logger before this datetime + :param log_level: If 'error', only shows errors. If 'warning', + only show warnings + """ + return self.get_instances(kwargs) + + def create(self, credential_type, **kwargs): + """ + Make a phone call to a number. + + :param str credential_type: The type of credential + :param str friendly_name: The friendly name of the credential. + + :return: A :class:`Credential` object + """ + kwargs["type"] = credential_type + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Credential + """ + return self.delete_instance(sid) + + def update(self, sid, credential_type, **kwargs): + """ + Updates the Credential instance identified by sid + :param sid: Credential instance identifier + :param credential_type: Credential type + :param friendly_name: Credential's friendly name + :param certificate: Credential's certificate + :param private_key: Credential's Private key + :param sandbox: Credential's Sandbox + :param api_key: Credential's Api Key + :return: Updated instance + """ + kwargs['type'] = credential_type + return self.update_instance(sid, kwargs) diff --git a/twilio/rest/resources/ip_messaging/members.py b/twilio/rest/resources/ip_messaging/members.py new file mode 100644 index 0000000000..dfdaadf149 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/members.py @@ -0,0 +1,68 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Member(NextGenInstanceResource): + + def update(self, role_sid, **kwargs): + """ + Updates the Member instance identified by sid + :param sid: Member instance identifier + :param role_sid: Member's Role Sid + :param identity: Member's Identity + :return: Updated instance + """ + kwargs['role_sid'] = role_sid + return self.update_instance(**kwargs) + + def delete(self): + """ + Delete this member + """ + return self.delete_instance() + + +class Members(NextGenListResource): + + name = "Members" + instance = Member + + def list(self, **kwargs): + """ + Returns a page of :class:`Member` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Member instance resource representation. + + """ + return self.get_instances(kwargs) + + def create(self, identity, **kwargs): + """ + Create a Member. + + :param str identity: The identity of the user. + :param str role: The role to assign the member. + + :return: A :class:`Member` object + """ + kwargs["identity"] = identity + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Member + """ + return self.delete_instance(sid) + + def update(self, sid, role_sid, **kwargs): + """ + Updates the Member instance identified by sid + :param sid: Member instance identifier + :param role_sid: Member's Role Sid + :param identity: Member's Identity + :return: Updated instance + """ + kwargs['role_sid'] = role_sid + return self.update_instance(sid, kwargs) diff --git a/twilio/rest/resources/ip_messaging/messages.py b/twilio/rest/resources/ip_messaging/messages.py new file mode 100644 index 0000000000..848ba16095 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/messages.py @@ -0,0 +1,68 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Message(NextGenInstanceResource): + + def update(self, **kwargs): + """ + Updates the Message instance + :param sid: Message instance identifier + :param service_sid: Service instance identifier + :param channel_sid: Channel instance identifier + :param body: Message's body + :return: the updated instance + """ + return self.update_instance(**kwargs) + + def delete(self): + """ + Delete this message + """ + return self.delete_instance() + + +class Messages(NextGenListResource): + + name = "Messages" + instance = Message + + def list(self, **kwargs): + """ + Returns a page of :class:`Message` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Message instance resource representation. + + """ + return self.get_instances(kwargs) + + def create(self, body, **kwargs): + """ + Create a Message. + + :param str body: The body of the message. + :param str from: The message author's identity. + + :return: A :class:`Message` object + """ + kwargs["body"] = body + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Message + """ + return self.delete_instance(sid) + + def update(self, sid, **kwargs): + """ + Updates the Message instance identified by sid + :param sid: Message instance identifier + :param service_sid: Service instance identifier + :param channel_sid: Channel instance identifier + :param body: Message's body + :return: the updated instance + """ + return self.update_instance(sid, kwargs) diff --git a/twilio/rest/resources/ip_messaging/roles.py b/twilio/rest/resources/ip_messaging/roles.py new file mode 100644 index 0000000000..3e1f232842 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/roles.py @@ -0,0 +1,67 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Role(NextGenInstanceResource): + + def update(self, permission, **kwargs): + """ + Updates this Role instance + :param permission: Role permission + :return: Updated instance + """ + kwargs['permission'] = permission + return self.update_instance(**kwargs) + + def delete(self): + """ + Delete this role + """ + return self.delete_instance() + + +class Roles(NextGenListResource): + + name = "Roles" + instance = Role + + def list(self, **kwargs): + """ + Returns a page of :class:`Role` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Role instance resource representation. + + """ + return self.get_instances(kwargs) + + def delete(self, sid): + """ + Delete a given Role + """ + return self.delete_instance(sid) + + def create(self, friendly_name, role_type, permission): + """ + Creates a Role + :param str friendly_name: Human readable name to the Role + :param str role_type: Type of role - deployment or channel + :param str permission: Set of permissions for the role + """ + kwargs = { + "friendly_name": friendly_name, + "type": role_type, + "permission": permission + } + return self.create_instance(kwargs) + + def update(self, sid, permission, **kwargs): + """ + Updates the Role instance identified by sid + :param sid: Role instance identifier + :param permission: Role permission + :return: Updated instance + """ + kwargs['permission'] = permission + return self.update_instance(sid, kwargs) diff --git a/twilio/rest/resources/ip_messaging/services.py b/twilio/rest/resources/ip_messaging/services.py new file mode 100644 index 0000000000..f22c30260a --- /dev/null +++ b/twilio/rest/resources/ip_messaging/services.py @@ -0,0 +1,69 @@ +from .channels import Channels +from .roles import Roles +from .users import Users +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Service(NextGenInstanceResource): + + subresources = [ + Channels, + Roles, + Users + ] + + def update(self, **kwargs): + """ + Updates this Service instance + :return: Updated instance + """ + return self.update_instance(**kwargs) + + def delete(self): + """ + Delete this service + """ + return self.delete_instance() + + +class Services(NextGenListResource): + + name = "Services" + instance = Service + + def list(self, **kwargs): + """ + Returns a page of :class:`Service` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Service instance resource representation. + + """ + return self.get_instances(kwargs) + + def create(self, friendly_name, **kwargs): + """ + Create a service. + + :param str friendly_name: The friendly name for the service + + :return: A :class:`Service` object + """ + kwargs["friendly_name"] = friendly_name + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Service + """ + return self.delete_instance(sid) + + def update(self, sid, **kwargs): + """ + Updates the Service instance identified by sid + :param sid: Service instance identifier + :return: Updated instance + """ + return self.update_instance(sid, kwargs) diff --git a/twilio/rest/resources/ip_messaging/users.py b/twilio/rest/resources/ip_messaging/users.py new file mode 100644 index 0000000000..1439772cc0 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/users.py @@ -0,0 +1,63 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class User(NextGenInstanceResource): + + def update(self, **kwargs): + """ + Updates this User instance + :param role_sid: The role to assign the user. + :return: Updated instance + """ + return self.update_instance(**kwargs) + + def delete(self): + """ + Delete this user + """ + return self.delete_instance() + + +class Users(NextGenListResource): + + name = "Users" + instance = User + + def list(self, **kwargs): + """ + Returns a page of :class:`User` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the User instance resource representation. + + """ + return self.get_instances(kwargs) + + def create(self, identity, **kwargs): + """ + Creates a User + + :param str identity: The identity of the user. + :param str role_sid: The role to assign the user. + + :return: A :class:`User` object + """ + kwargs["identity"] = identity + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given User + """ + return self.delete_instance(sid) + + def update(self, sid, **kwargs): + """ + Updates the User instance identified by sid + :param sid: User instance identifier + :param role_sid: The role to assign the user. + :return: Updated instance + """ + return self.update_instance(sid, kwargs) diff --git a/twilio/rest/resources/keys.py b/twilio/rest/resources/keys.py new file mode 100644 index 0000000000..f68698232c --- /dev/null +++ b/twilio/rest/resources/keys.py @@ -0,0 +1,75 @@ +from twilio.rest.resources.base import InstanceResource, ListResource + + +class Key(InstanceResource): + """ + A key resource. + See https://www.twilio.com/docs/api/rest-keys + + .. attribute:: sid + + The unique ID for this key. + + .. attribute:: friendly_name + + A human-readable description of this key. + + .. attribute:: secret + + This key's secret. + + .. attribute:: date_created + + The date this key was created, given as UTC in ISO 8601 format. + + .. attribute:: date_updated + + The date this singing key was last updated, given as UTC in ISO 8601 + format. + """ + + def update(self, **kwargs): + """ + Update this key + """ + return self.parent.update(self.name, **kwargs) + + def delete(self): + """ + Delete this key + """ + return self.parent.delete(self.name) + + +class Keys(ListResource): + name = "Keys" + key = "keys" + instance = Key + + def create(self, **kwargs): + """ + Create a :class:`Key` with any of these optional parameters. + + :param friendly_name: A human readable description of the signing key. + """ + return self.create_instance(kwargs) + + def update(self, sid, **kwargs): + """ + Update a :class:`Key` with the given parameters. + + All the parameters are describe above in :meth:`create` + """ + return self.update_instance(sid, kwargs) + + def delete(self, sid): + """ + Delete a :class:`Key` + """ + return self.delete_instance(sid) + + def list(self, **kwargs): + """ + Returns a page of :class:`Key` resources as a list + """ + return self.get_instances(kwargs) diff --git a/twilio/rest/resources/messages.py b/twilio/rest/resources/messages.py index 90779b1bc2..a554975268 100644 --- a/twilio/rest/resources/messages.py +++ b/twilio/rest/resources/messages.py @@ -116,7 +116,7 @@ def create(self, from_=None, **kwargs): :param status_callback: A URL that Twilio will POST to when your message is processed. :param str application_sid: The 34 character sid of the application - Twilio should use to handle this phone call. + Twilio should use to handle this message. """ kwargs["from"] = from_ return self.create_instance(kwargs) diff --git a/twilio/rest/resources/phone_numbers.py b/twilio/rest/resources/phone_numbers.py index 91b0042210..16557d3c21 100644 --- a/twilio/rest/resources/phone_numbers.py +++ b/twilio/rest/resources/phone_numbers.py @@ -325,7 +325,8 @@ def search(self, **kwargs): :param str region: When searching the US, show numbers in this state :param str postal_code: Only show numbers in this area code :param str rate_center: US only. - :param tuple near_lat_long: Find close numbers within Distance miles. + :param str near_lat_long: Find close numbers within Distance miles. + Should be string of format "{lat},{long}" :param integer distance: Search radius for a Near- query in miles. :param boolean beta: Whether to include numbers new to the Twilio platform. diff --git a/twilio/rest/resources/pricing/__init__.py b/twilio/rest/resources/pricing/__init__.py index b1797a0a87..1edd5d260b 100644 --- a/twilio/rest/resources/pricing/__init__.py +++ b/twilio/rest/resources/pricing/__init__.py @@ -11,3 +11,7 @@ PhoneNumberCountry, PhoneNumbers, ) + +from .messaging_countries import ( + MessagingCountries +) diff --git a/twilio/rest/resources/pricing/messaging_countries.py b/twilio/rest/resources/pricing/messaging_countries.py new file mode 100644 index 0000000000..2be1a6e097 --- /dev/null +++ b/twilio/rest/resources/pricing/messaging_countries.py @@ -0,0 +1,39 @@ +from .. import NextGenInstanceResource, NextGenListResource + + +class MessagingCountry(NextGenInstanceResource): + """Pricing information for Twilio Messages in a specific country. + + .. attribute:: iso_country + + The country's 2-character ISO 3166-1 code. + + """ + id_key = "iso_country" + + +class MessagingCountries(NextGenListResource): + """A list of countries where Twilio Messages are available. + + The returned list of MessagingCountry objects will not have pricing + information populated. To get pricing information for a specific country, + retrieve it with the :meth:`get` method. + """ + + instance = MessagingCountry + key = "countries" + name = "Countries" + + def get(self, iso_country): + """Retrieve pricing information for Twilio Messages in the specified + country. + + :param iso_country: The two-letter ISO code for the country + """ + return self.get_instance(iso_country) + + def list(self, **kwargs): + """Retrieve the list of countries in which Twilio Messages are + available.""" + + return super(MessagingCountries, self).list(**kwargs) diff --git a/twilio/rest/resources/recordings.py b/twilio/rest/resources/recordings.py index 6a4dc227da..be88909144 100644 --- a/twilio/rest/resources/recordings.py +++ b/twilio/rest/resources/recordings.py @@ -41,6 +41,18 @@ def list(self, before=None, after=None, **kwargs): kwargs["DateCreated>"] = after return self.get_instances(kwargs) + @normalize_dates + def iter(self, before=None, after=None, **kwargs): + """ + Returns an iterator of :class:`Recording` resources. + + :param date after: Only list recordings logged after this datetime + :param date before: Only list recordings logger before this datetime + """ + kwargs["DateCreated<"] = before + kwargs["DateCreated>"] = after + return super(Recordings, self).iter(**kwargs) + def delete(self, sid): """ Delete the given recording diff --git a/twilio/rest/resources/task_router/workers.py b/twilio/rest/resources/task_router/workers.py index f257435cb9..6383a57d0c 100644 --- a/twilio/rest/resources/task_router/workers.py +++ b/twilio/rest/resources/task_router/workers.py @@ -1,5 +1,6 @@ from .. import NextGenInstanceResource, NextGenListResource from .statistics import Statistics +from .reservations import Reservations class Worker(NextGenInstanceResource): @@ -68,7 +69,8 @@ class Worker(NextGenInstanceResource): calculate :class: `Workflow` statistics. """ subresources = [ - Statistics + Statistics, + Reservations ] def delete(self): diff --git a/twilio/rest/resources/trunking/__init__.py b/twilio/rest/resources/trunking/__init__.py new file mode 100644 index 0000000000..887e5d9d3f --- /dev/null +++ b/twilio/rest/resources/trunking/__init__.py @@ -0,0 +1,24 @@ +from .credential_lists import ( + CredentialList, + CredentialLists +) + +from .ip_access_control_lists import ( + IpAccessControlList, + IpAccessControlLists +) + +from .origination_urls import ( + OriginationUrl, + OriginationUrls +) + +from .phone_numbers import ( + PhoneNumber, + PhoneNumbers +) + +from .trunks import ( + Trunk, + Trunks +) diff --git a/twilio/rest/resources/trunking/credential_lists.py b/twilio/rest/resources/trunking/credential_lists.py new file mode 100644 index 0000000000..027063ef09 --- /dev/null +++ b/twilio/rest/resources/trunking/credential_lists.py @@ -0,0 +1,59 @@ +from .. import NextGenInstanceResource, NextGenListResource + + +class CredentialList(NextGenInstanceResource): + """ + A Credential List Resource. + See the `SIP Trunking API reference + _` + for more information. + + .. attribute:: sid + + The unique ID for this Credential List. + + .. attribute:: trunk_sid + + The unique ID of the Trunk that owns this Credential List. + """ + + def delete(self): + """ + Disassociates a Credential List from the trunk. + """ + return self.parent.delete_instance(self.name) + + +class CredentialLists(NextGenListResource): + """ A list of Credential List resources """ + + name = "CredentialLists" + instance = CredentialList + key = "credential_lists" + + def list(self, **kwargs): + """ + Retrieve the list of Credential List resources for a given trunk sid. + :param Page: The subset of results that needs to be fetched + :param PageSize: The size of the Page that needs to be fetched + """ + return super(CredentialLists, self).list(**kwargs) + + def create(self, credential_list_sid): + """ + Associate a Credential List with a Trunk. + + :param credential_list_sid: A human readable Credential list sid. + """ + data = { + 'credential_list_sid': credential_list_sid + } + return self.create_instance(data) + + def delete(self, credential_list_sid): + """ + Disassociates a Credential List from the Trunk. + + :param credential_list_sid: A human readable Credential list sid. + """ + return self.delete_instance(credential_list_sid) diff --git a/twilio/rest/resources/trunking/ip_access_control_lists.py b/twilio/rest/resources/trunking/ip_access_control_lists.py new file mode 100644 index 0000000000..73d2656f4e --- /dev/null +++ b/twilio/rest/resources/trunking/ip_access_control_lists.py @@ -0,0 +1,61 @@ +from .. import NextGenInstanceResource, NextGenListResource + + +class IpAccessControlList(NextGenInstanceResource): + """ + An IP Access Control List Resource. + See the `SIP Trunking API reference + _` + for more information. + + .. attribute:: sid + + The unique ID for this IP Access Control List. + + .. attribute:: trunk_sid + + The unique ID of the Trunk that owns this Credential List. + """ + + def delete(self): + """ + Disassociate an Ip Access Control List. + """ + return self.parent.delete_instance(self.name) + + +class IpAccessControlLists(NextGenListResource): + """ A list of IP Access Control List resources """ + + name = "IpAccessControlLists" + instance = IpAccessControlList + key = "ip_access_control_lists" + + def list(self, **kwargs): + """ + Retrieve the IP Access Control List resources. + :param Page: The subset of results that needs to be fetched + :param PageSize: The size of the Page that needs to be fetched + """ + return super(IpAccessControlLists, self).list(**kwargs) + + def create(self, ip_access_control_list_sid): + """ + Associate an IP Access Control List with a Trunk. + + :param ip_access_control_list_sid: + A human readable IP Access Control list sid. + """ + data = { + 'ip_access_control_list_sid': ip_access_control_list_sid + } + return self.create_instance(data) + + def delete(self, ip_access_control_list_sid): + """ + Disassociate an Ip Access Control List from the Trunk. + + :param ip_access_control_list_sid: + A human readable IP Access Control list sid. + """ + return self.delete_instance(ip_access_control_list_sid) diff --git a/twilio/rest/resources/trunking/origination_urls.py b/twilio/rest/resources/trunking/origination_urls.py new file mode 100644 index 0000000000..6cb6d4a0c2 --- /dev/null +++ b/twilio/rest/resources/trunking/origination_urls.py @@ -0,0 +1,89 @@ +from .. import NextGenInstanceResource, NextGenListResource + + +class OriginationUrl(NextGenInstanceResource): + """ + An Origination URL resource. + See the `TaskRouter API reference + _` + for more information. + + .. attribute:: sid + + The unique ID for this Origination URL. + + .. attribute:: trunk_sid + + The unique ID of the Trunk that owns this Credential List. + """ + + def delete(self): + """ + Delete an Origination URL. + """ + return self.parent.delete_instance(self.name) + + def update(self, **kwargs): + """ + Update an Origination URL. + """ + return self.parent.update_instance(self.name, kwargs) + + +class OriginationUrls(NextGenListResource): + """ A list of Origination URL resources """ + + name = "OriginationUrls" + instance = OriginationUrl + key = "origination_urls" + + def create(self, friendly_name, sip_url, priority, weight, enabled): + """ + Create a Origination URL. + + :param friendly_name: A human readable descriptive text, up to 64 + characters long. + :param sip_url: The SIP address you want Twilio to route your + Origination calls to. This must be a sip: schema. + :param priority: Priority ranks the importance of the URI. Value + ranges from 0 - 65535. + :param weight: Weight is used to determine the share of load when + more than one URI has the same priority. + Value ranges from 0 - 65535. + :param enabled: A boolean value indicating whether the URL is + enabled or disabled. + + """ + data = { + 'friendly_name': friendly_name, + 'sip_url': sip_url, + 'priority': priority, + 'weight': weight, + 'enabled': enabled + } + return self.create_instance(data) + + def list(self, **kwargs): + """ + Retrieve the list of Origination URL resources for a given trunk sid. + :param Page: The subset of results that needs to be fetched + :param PageSize: The size of the Page that needs to be fetched + """ + return super(OriginationUrls, self).list(**kwargs) + + def update(self, origination_url_sid, data): + """ + Update an Origination Url. + + :param origination_url_sid: A human readable Origination Url sid. + :param data: Attributes that needs to be updated. + """ + return self.update_instance(origination_url_sid, data) + + def delete(self, origination_url_sid): + """ + Delete an Origination Url. + + :param origination_url_sid: A human readable Origination Url sid. + """ + return self.delete_instance(origination_url_sid) diff --git a/twilio/rest/resources/trunking/phone_numbers.py b/twilio/rest/resources/trunking/phone_numbers.py new file mode 100644 index 0000000000..90c3188160 --- /dev/null +++ b/twilio/rest/resources/trunking/phone_numbers.py @@ -0,0 +1,59 @@ +from .. import NextGenInstanceResource, NextGenListResource + + +class PhoneNumber(NextGenInstanceResource): + """ + A Phone Number resource. + See the `TaskRouter API reference + _` + for more information. + + .. attribute:: sid + + The unique ID for this Phone Number. + + .. attribute:: trunk_sid + + The unique ID of the Trunk that owns this Phone Number. + """ + + def delete(self): + """ + Removes an associated Phone Number from a Trunk. + """ + return self.parent.delete_instance(self.name) + + +class PhoneNumbers(NextGenListResource): + """ A list of Phone Numbers resources """ + + name = "PhoneNumbers" + instance = PhoneNumber + key = "phone_numbers" + + def list(self, **kwargs): + """ + Retrieves the list of Phone Number resources for a given trunk sid. + :param Page: The subset of results that needs to be fetched + :param PageSize: The size of the Page that needs to be fetched + """ + return super(PhoneNumbers, self).list(**kwargs) + + def create(self, phone_number_sid): + """ + Associates a Phone Number with the given Trunk. + + :param phone_number_sid: + Associates a Phone Number with the given trunk. + """ + data = { + 'phone_number_sid': phone_number_sid + } + return self.create_instance(data) + + def delete(self, sid): + """ + Disassociates a phone number from the trunk. + :param sid: Phone Number Sid + """ + return self.delete_instance(sid) diff --git a/twilio/rest/resources/trunking/trunks.py b/twilio/rest/resources/trunking/trunks.py new file mode 100644 index 0000000000..024f9777b1 --- /dev/null +++ b/twilio/rest/resources/trunking/trunks.py @@ -0,0 +1,65 @@ +from .. import NextGenInstanceResource, NextGenListResource + + +class Trunk(NextGenInstanceResource): + """ + A Trunk resource. + See the `TaskRouter API reference + _` + for more information. + + .. attribute:: sid + + The unique ID for this Trunk. + """ + + def delete(self): + """ + Deletes a Trunk. + """ + return self.parent.delete_instance(self.name) + + def update(self, **kwargs): + """ + Updates a Trunk. + + """ + return self.parent.update_instance(self.name, **kwargs) + + +class Trunks(NextGenListResource): + """ A list of Trunk resources """ + + name = "Trunks" + instance = Trunk + key = "trunks" + + def list(self, **kwargs): + """ + Retrieve the list of Trunk resources. + + :param Page: The subset of results that needs to be fetched + :param PageSize: The size of the Page that needs to be fetched + """ + return super(Trunks, self).list(**kwargs) + + def create(self, **kwargs): + """ + Creates a Trunk. + """ + return self.create_instance(kwargs) + + def update(self, sid, body): + """ + Updates a Trunk. + :param sid: A human readable 34 character unique identifier + :param body: Request body + """ + return self.update_instance(sid, body) + + def delete(self, sid): + """ + Deletes a Trunk. + :param sid: A human readable 34 character unique identifier + """ + return self.delete_instance(sid) diff --git a/twilio/rest/task_router.py b/twilio/rest/task_router.py index f9672d2820..eb93b29fd4 100644 --- a/twilio/rest/task_router.py +++ b/twilio/rest/task_router.py @@ -25,12 +25,13 @@ class TwilioTaskRouterClient(TwilioClient): def __init__(self, account=None, token=None, base="https://taskrouter.twilio.com", version="v1", - timeout=UNSET_TIMEOUT): + timeout=UNSET_TIMEOUT, request_account=None): """ Create a Twilio REST API client. """ super(TwilioTaskRouterClient, self).__init__(account, token, base, - version, timeout) + version, timeout, + request_account) self.base_uri = "{0}/{1}".format(base, version) self.workspace_uri = "{0}/Workspaces".format(self.base_uri) @@ -61,6 +62,15 @@ def reservations(self, workspace_sid, task_sid): workspace_sid, task_sid) return Reservations(base_uri, self.auth, self.timeout) + def worker_reservations(self, workspace_sid, worker_sid): + """ + Return a :class:`Reservations` instance for the :class:`Reservation` + with the given workspace_sid ans worker_sid + """ + base_uri = "{0}/{1}/Workers/{2}".format(self.workspace_uri, + workspace_sid, worker_sid) + return Reservations(base_uri, self.auth, self.timeout) + def task_queues(self, workspace_sid): """ Return a :class:`TaskQueues` instance for the :class:`TaskQueue` with diff --git a/twilio/rest/trunking.py b/twilio/rest/trunking.py new file mode 100644 index 0000000000..b733db5268 --- /dev/null +++ b/twilio/rest/trunking.py @@ -0,0 +1,71 @@ +from twilio.rest.base import TwilioClient +from twilio.rest.resources.trunking import ( + CredentialLists, + IpAccessControlLists, + OriginationUrls, + PhoneNumbers, + Trunks +) +from twilio.rest.resources import UNSET_TIMEOUT + + +class TwilioTrunkingClient(TwilioClient): + """ + A client for accessing the Twilio Trunking API + + :param str account: Your Account SID from `your dashboard + `_ + :param str token: Your Auth Token from `your dashboard + `_ + :param float timeout: The socket and read timeout for requests to Twilio + """ + + def __init__(self, account=None, token=None, + base="https://trunking.twilio.com", version="v1", + timeout=UNSET_TIMEOUT, request_account=None): + """ + Create a Twilio REST API client. + """ + super(TwilioTrunkingClient, self).__init__(account, token, base, + version, timeout, + request_account) + self.trunk_base_uri = "{0}/{1}".format(base, version) + + def credential_lists(self, trunk_sid): + """ + Return a :class:`CredentialList` instance + """ + credential_lists_uri = "{0}/Trunks/{1}".format( + self.trunk_base_uri, trunk_sid) + return CredentialLists(credential_lists_uri, self.auth, self.timeout) + + def ip_access_control_lists(self, trunk_sid): + """ + Return a :class:`IpAccessControlList` instance + """ + ip_access_control_lists_uri = "{0}/Trunks/{1}".format( + self.trunk_base_uri, trunk_sid) + return IpAccessControlLists(ip_access_control_lists_uri, self.auth, + self.timeout) + + def origination_urls(self, trunk_sid): + """ + Return a :class:`OriginationUrls` instance + """ + origination_urls_uri = "{0}/Trunks/{1}".format( + self.trunk_base_uri, trunk_sid) + return OriginationUrls(origination_urls_uri, self.auth, self.timeout) + + def phone_numbers(self, trunk_sid): + """ + Return a :class:`PhoneNumbers` instance + """ + phone_numbers_uri = "{0}/Trunks/{1}".format(self.trunk_base_uri, + trunk_sid) + return PhoneNumbers(phone_numbers_uri, self.auth, self.timeout) + + def trunks(self): + """ + Return a :class:`Trunks` instance + """ + return Trunks(self.trunk_base_uri, self.auth, self.timeout) diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index 17975c1cba..ae892eda49 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -1,120 +1,206 @@ import time - from .. import jwt +from .taskrouter_config import TaskRouterConfig +from .workflow_config import WorkflowConfig +from .workflow_ruletarget import WorkflowRuleTarget +from .workflow_rule import WorkflowRule +import warnings +warnings.simplefilter('always', DeprecationWarning) TASK_ROUTER_BASE_URL = 'https://taskrouter.twilio.com' -TASK_ROUTER_BASE_WS_URL = 'https://event-bridge.twilio.com/v1/wschannels' +TASK_ROUTER_BASE_EVENTS_URL = 'https://event-bridge.twilio.com/v1/wschannels' +TASK_ROUTER_VERSION = "v1" REQUIRED = {'required': True} OPTIONAL = {'required': False} +def deprecated(func): + def log_warning(*args, **kwargs): + # stacklevel = 2 makes the warning refer to the caller of the + # deprecation rather than the source of deprecation itself + warnings.warn("Call to deprecated function {0}.". + format(func.__name__), + stacklevel=2, + category=DeprecationWarning) + return func(*args, **kwargs) + return log_warning + + class TaskRouterCapability(object): - """ - A token to control permissions for the TaskRouter service. - - :param str account_sid: The account to generate a token for - :param str auth_token: The auth token for the account. Used to sign the - token and will not be included in generated output. - :param str workspace_sid: The workspace to grant capabilities over - :param str worker_sid: The worker sid to grant capabilities over - :param str base_url: The base TaskRouter API URL - :param str base_ws_url: The base TaskRouter event stream URL - """ - def __init__(self, account_sid, auth_token, workspace_sid, worker_sid, - base_url=TASK_ROUTER_BASE_URL, - version='v1', - base_ws_url=TASK_ROUTER_BASE_WS_URL): + def __init__(self, account_sid, auth_token, workspace_sid, channel_id): self.account_sid = account_sid self.auth_token = auth_token - self.workspace_sid = workspace_sid - self.worker_sid = worker_sid - self.version = version - self.base_url = '%s/%s' % (base_url, self.version) - self.base_ws_url = base_ws_url self.policies = [] - self._allow_worker_websocket_urls() - self._allow_activity_list_fetch() + self.workspace_sid = workspace_sid + self.channel_id = channel_id + self.base_url = "{0}/{1}/Workspaces/{2}".format(TASK_ROUTER_BASE_URL, + TASK_ROUTER_VERSION, + workspace_sid) - @property - def workspace_url(self): - return '%s/Workspaces/%s' % (self.base_url, self.workspace_sid) + # validate the JWT + self.validate_jwt() + + # add permissions to GET and POST to the event-bridge channel + self.allow_web_sockets(channel_id) + + # set up resources + self.setup_resource() + + # add permissions to fetch the instance resource + self.add_policy(self.resource_url, "GET", True) @property - def worker_url(self): - return '%s/Workers/%s' % (self.workspace_url, self.worker_sid) - - def _allow_worker_websocket_urls(self): - worker_event_url = '%s/%s/%s' % ( - self.base_ws_url, - self.account_sid, - self.worker_sid, - ) - self.policies.append(make_policy( - worker_event_url, - 'GET', - )) - self.policies.append(make_policy( - worker_event_url, - 'POST', - )) + def channel_prefix(self): + return self.channel_id[0:2] - def _allow_activity_list_fetch(self): - self.policies.append(make_policy( - '%s/Activities' % self.workspace_url, - 'GET', - )) + def setup_resource(self): + if self.channel_prefix == "WS": + self.resource_url = self.base_url + elif self.channel_prefix == "WK": + self.resource_url = self.base_url + "/Workers/" + self.channel_id - def allow_worker_activity_updates(self): - self.policies.append(make_policy( - self.worker_url, - 'POST', - post_filter={'ActivitySid': REQUIRED}, - )) + activity_url = self.base_url + "/Activities" + self.allow(activity_url, "GET") + + tasks_url = self.base_url + "/Tasks/**" + self.allow(tasks_url, "GET") + + worker_reservations_url = self.resource_url + "/Reservations/**" + self.allow(worker_reservations_url, "GET") + elif self.channel_prefix == "WQ": + self.resource_url = "{0}/TaskQueues/{1}".format( + self.base_url, self.channel_id) + + def allow_web_sockets(self, channel_id): + web_socket_url = "{0}/{1}/{2}".format(TASK_ROUTER_BASE_EVENTS_URL, + self.account_sid, + self.channel_id) + + self.policies.append(self.make_policy(web_socket_url, "GET", True)) + self.policies.append(self.make_policy(web_socket_url, "POST", True)) + + def validate_jwt(self): + if self.account_sid is None or self.account_sid[0:2] != "AC": + raise ValueError('Invalid AccountSid provided: ' + + self.account_sid) + if self.workspace_sid is None or self.workspace_sid[0:2] != "WS": + raise ValueError('Invalid WorkspaceSid provided: ' + + self.workspace_sid) + if self.channel_id is None: + raise ValueError('ChannelId not provided') + + if self.channel_prefix != "WS" and self.channel_prefix != "WK" \ + and self.channel_prefix != "WQ": + raise ValueError('Invalid ChannelId provided: ' + self.channel_id) + + def allow_fetch_subresources(self): + self.allow(self.resource_url + "/**", "GET") + + def allow_updates(self): + self.allow(self.resource_url, "POST") + + def allow_updates_subresources(self): + self.allow(self.resource_url + "/**", "POST") + + def allow_delete(self): + self.allow(self.resource_url, "DELETE") + + def allow_delete_subresources(self): + self.allow(self.resource_url + "/**", "DELETE") + + @deprecated def allow_worker_fetch_attributes(self): - self.policies.append(make_policy( - self.worker_url, - 'GET', - )) + if self.channel_prefix != "WK": + raise ValueError("Deprecated func not applicable to non Worker") + else: + self.policies.append(self.make_policy( + self.resource_url, + 'GET')) + @deprecated + def allow_worker_activity_updates(self): + if self.channel_prefix == "WK": + self.policies.append(self.make_policy( + self.resource_url, + 'POST', + True, + post_filter={'ActivitySid': REQUIRED})) + else: + raise ValueError("Deprecated func not applicable to non Worker") + + @deprecated def allow_task_reservation_updates(self): - tasks_url = '%s/Tasks/**' % self.workspace_url - self.policies.append(make_policy( - tasks_url, - 'POST', - post_filter={'ReservationStatus': REQUIRED}, - )) + if self.channel_prefix == "WK": + tasks_url = self.base_url + "/Tasks/**" + self.policies.append(self.make_policy( + tasks_url, + 'POST', + True)) + else: + raise ValueError("Deprecated func not applicable to non Worker") - def generate_token(self, ttl=3600, attributes=None): - """ - Generate a token string based on the credentials and permissions - previously configured on this object. + def add_policy(self, url, method, + allowed, query_filter=None, post_filter=None): + + policy = self.make_policy(url, method, + allowed, query_filter, post_filter) + self.policies.append(policy) + + def allow(self, url, method, query_filter=None, post_filter=None): + self.add_policy(url, method, True, query_filter, post_filter) + + def deny(self, url, method, query_filter=None, post_filter=None): + self.add_policy(url, method, False, query_filter, post_filter) - :param int ttl: Expiration time in seconds of the token. Defaults to - 3600 seconds (1 hour). - :param dict attributes: Extra attributes to pass into the token. + def make_policy(self, url, method, + allowed=True, query_filter=None, post_filter=None): + + """Create a policy dictionary for the given resource and method. + :param str url: the resource URL to grant or deny access to + :param str method: the HTTP method to allow or deny + :param allowed bool: whether this request is allowed + :param dict query_filter: specific GET parameter names + to require or allow + :param dict post_filter: POST parameter names + to require or allow """ - return self._generate_token( - ttl, - { - 'account_sid': self.account_sid, - 'channel': self.worker_sid, - 'worker_sid': self.worker_sid, - 'workspace_sid': self.workspace_sid, - } - ) + return { + 'url': url, + 'method': method, + 'allow': allowed, + 'query_filter': query_filter or {}, + 'post_filter': post_filter or {} + } + + def get_resource_url(self): + return self.resource_url + + def generate_token(self, ttl=3600): + task_router_attributes = { + 'account_sid': self.account_sid, + 'workspace_sid': self.workspace_sid, + 'channel': self.channel_id + } + + if self.channel_prefix == "WK": + task_router_attributes["worker_sid"] = self.channel_id + elif self.channel_prefix == "WQ": + task_router_attributes["taskqueue_sid"] = self.channel_id + + return self._generate_token(ttl, task_router_attributes) def _generate_token(self, ttl, attributes=None): payload = { - 'version': self.version, - 'friendly_name': self.worker_sid, - 'policies': self.policies, 'iss': self.account_sid, 'exp': int(time.time()) + ttl, + 'version': TASK_ROUTER_VERSION, + 'friendly_name': self.channel_id, + 'policies': self.policies, } if attributes is not None: @@ -123,22 +209,55 @@ def _generate_token(self, ttl, attributes=None): return jwt.encode(payload, self.auth_token, 'HS256') -def make_policy(url, method, query_filter=None, post_filter=None, - allowed=True): - """ - Create a policy dictionary for the given resource and method. - - :param str url: the resource URL to grant or deny access to - :param str method: the HTTP method to allow or deny - :param dict query_filter: specific GET parameter names to require or allow - :param dict post_filter: POST parameter names to require or allow - :param allowed bool: whether this request is allowed - """ - - return { - 'url': url, - 'method': method, - 'allow': allowed, - 'query_filter': query_filter or {}, - 'post_filter': post_filter or {}, - } +class TaskRouterWorkerCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid, worker_sid): + super(TaskRouterWorkerCapability, self).__init__(account_sid, + auth_token, + workspace_sid, + worker_sid) + + self.activity_url = self.base_url + "/Activities" + self.reservations_url = self.base_url + "/Tasks/**" + self.worker_reservations_url = self.resource_url + "/Reservations/**" + + # add permissions to fetch the + # list of activities, tasks, and worker reservations + self.allow(self.activity_url, "GET") + self.allow(self.reservations_url, "GET") + self.allow(self.worker_reservations_url, "GET") + + def setup_resource(self): + self.resource_url = self.base_url + "/Workers/" + self.channel_id + + def allow_activity_updates(self): + self.policies.append(self.make_policy( + self.resource_url, + 'POST', + True, + post_filter={'ActivitySid': REQUIRED})) + + def allow_reservation_updates(self): + self.policies.append(self.make_policy( + self.reservations_url, + 'POST', + True)) + self.policies.append(self.make_policy( + self.worker_reservations_url, + 'POST', + True)) + + +class TaskRouterTaskQueueCapability(TaskRouterCapability): + def setup_resource(self): + self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id + + +class TaskRouterWorkspaceCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid): + super(TaskRouterWorkspaceCapability, self).__init__(account_sid, + auth_token, + workspace_sid, + workspace_sid) + + def setup_resource(self): + self.resource_url = self.base_url diff --git a/twilio/task_router/taskrouter_config.py b/twilio/task_router/taskrouter_config.py new file mode 100644 index 0000000000..22c04f1913 --- /dev/null +++ b/twilio/task_router/taskrouter_config.py @@ -0,0 +1,23 @@ +from .workflow_rule import WorkflowRule +from .workflow_ruletarget import WorkflowRuleTarget + + +class TaskRouterConfig: + + """ + TaskRouterConfig represents the filter and default_filter + of a workflow configuration of taskrouter + """ + + def __init__(self, rules, default_target): + self.filters = rules + self.default_filter = default_target + + for rule in self.filters: + if not isinstance(rule, WorkflowRule): + filter_friendly_name = rule.pop('filter_friendly_name', None) + if filter_friendly_name is not None: + rule['friendly_name'] = filter_friendly_name + + def __repr__(self): + return self.__dict__ diff --git a/twilio/task_router/workflow_config.py b/twilio/task_router/workflow_config.py new file mode 100644 index 0000000000..e9c27aa378 --- /dev/null +++ b/twilio/task_router/workflow_config.py @@ -0,0 +1,26 @@ +from .taskrouter_config import TaskRouterConfig +import json + + +class WorkflowConfig: + + """ + WorkflowConfig represents the whole workflow config json which contains + filters and default_filter. + """ + + def __init__(self, workflow_rules, default_target): + # filters and default_filters + self.task_routing = TaskRouterConfig(workflow_rules, default_target) + + def to_json(self): + return json.dumps(self, + default=lambda o: o.__dict__, + sort_keys=True, + indent=4) + + @staticmethod + def json2obj(data): + m = json.loads(data) + return WorkflowConfig(m['task_routing']['filters'], + m['task_routing']['default_filter']) diff --git a/twilio/task_router/workflow_rule.py b/twilio/task_router/workflow_rule.py new file mode 100644 index 0000000000..5c4c1fd71c --- /dev/null +++ b/twilio/task_router/workflow_rule.py @@ -0,0 +1,34 @@ +from .workflow_ruletarget import WorkflowRuleTarget + + +class WorkflowRule: + + """ + WorkflowRule represents the top level filter + which contains a 1 or more targets + + ..attribute::expression + + The expression at the top level filter + + ..attribute::targets + + The list of targets under the filter + + ..attribute::friendlyName + + The name of the filter + """ + + def __init__(self, expression, targets, friendly_name): + + self.expression = expression + self.targets = targets + self.friendly_name = friendly_name + + def __repr__(self): + return str({ + 'expression': self.expression, + 'friendly_name': self.friendly_name, + 'targets': self.targets, + }) diff --git a/twilio/task_router/workflow_ruletarget.py b/twilio/task_router/workflow_ruletarget.py new file mode 100644 index 0000000000..1cee506c30 --- /dev/null +++ b/twilio/task_router/workflow_ruletarget.py @@ -0,0 +1,27 @@ +class WorkflowRuleTarget: + """ + Workflow Rule target which is encompassed + inside targets + + ..attribute::queue + + The queue which will handle the task matching this filter target + + ..attribute::expression + + The dynamic expression if any for this matching + + ..attribute::priority + + The priority for the target + + ..attribute::timeout + + The timeout before the reservation expires. + """ + def __init__(self, queue, expression, priority, timeout): + + self.queue = queue + self.expression = expression + self.priority = priority + self.timeout = timeout diff --git a/twilio/twiml.py b/twilio/twiml.py index f05015842d..cbac1122da 100644 --- a/twilio/twiml.py +++ b/twilio/twiml.py @@ -524,10 +524,25 @@ class Enqueue(Verb): GET = 'GET' POST = 'POST' + nestables = ['Task'] + def __init__(self, name, **kwargs): super(Enqueue, self).__init__(**kwargs) self.body = name + def task(self, attributes, **kwargs): + return self.append(Task(attributes, **kwargs)) + + +class Task(Verb): + """Specify the task attributes when enqueuing a call + + :param attributes: Attributes for a task + """ + def __init__(self, attributes, **kwargs): + super(Task, self).__init__(**kwargs) + self.body = attributes + class Leave(Verb): """Signals the call to leave its queue diff --git a/twilio/version.py b/twilio/version.py index 953ebe5125..fe171cc661 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('4', '4', '0') +__version_info__ = ('5', '6', '0') __version__ = '.'.join(__version_info__)