From a1df6d1250a54586e1fbcf16dcd0e99a027d0a49 Mon Sep 17 00:00:00 2001 From: remi-stripe Date: Tue, 14 May 2019 11:55:21 -0700 Subject: [PATCH] Add support for the Capability resource and APIs (#566) --- .travis.yml | 2 +- stripe/api_resources/__init__.py | 1 + .../abstract/nested_resource_class_methods.py | 10 +++-- stripe/api_resources/account.py | 8 ++++ stripe/api_resources/capability.py | 32 ++++++++++++++ stripe/util.py | 1 + tests/api_resources/test_account.py | 33 ++++++++++++++ tests/api_resources/test_capability.py | 43 +++++++++++++++++++ tests/conftest.py | 2 +- 9 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 stripe/api_resources/capability.py create mode 100644 tests/api_resources/test_capability.py diff --git a/.travis.yml b/.travis.yml index 40a3e9e3d..58f09adf0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ cache: env: global: # If changing this number, please also change it in `tests/conftest.py`. - - STRIPE_MOCK_VERSION=0.54.0 + - STRIPE_MOCK_VERSION=0.56.0 before_install: # Unpack and start stripe-mock so that the test suite can talk to it diff --git a/stripe/api_resources/__init__.py b/stripe/api_resources/__init__.py index 3a15799fa..e65b87ef1 100644 --- a/stripe/api_resources/__init__.py +++ b/stripe/api_resources/__init__.py @@ -15,6 +15,7 @@ from stripe.api_resources.bank_account import BankAccount from stripe.api_resources.bitcoin_receiver import BitcoinReceiver from stripe.api_resources.bitcoin_transaction import BitcoinTransaction +from stripe.api_resources.capability import Capability from stripe.api_resources.card import Card from stripe.api_resources.charge import Charge from stripe.api_resources.country_spec import CountrySpec diff --git a/stripe/api_resources/abstract/nested_resource_class_methods.py b/stripe/api_resources/abstract/nested_resource_class_methods.py index 03ff8587b..e1bcf9fab 100644 --- a/stripe/api_resources/abstract/nested_resource_class_methods.py +++ b/stripe/api_resources/abstract/nested_resource_class_methods.py @@ -4,9 +4,13 @@ from stripe.six.moves.urllib.parse import quote_plus -def nested_resource_class_methods(resource, path=None, operations=None): +def nested_resource_class_methods( + resource, path=None, operations=None, resource_plural=None +): + if resource_plural is None: + resource_plural = "%ss" % resource if path is None: - path = "%ss" % resource + path = resource_plural if operations is None: raise ValueError("operations list required") @@ -109,7 +113,7 @@ def list_nested_resources(cls, id, **params): "get", url, **params ) - list_method = "list_%ss" % resource + list_method = "list_%s" % resource_plural setattr(cls, list_method, classmethod(list_nested_resources)) else: diff --git a/stripe/api_resources/account.py b/stripe/api_resources/account.py index 96169e5ed..1c94be48c 100644 --- a/stripe/api_resources/account.py +++ b/stripe/api_resources/account.py @@ -12,6 +12,11 @@ @custom_method("reject", http_verb="post") +@nested_resource_class_methods( + "capability", + operations=["retrieve", "update", "list"], + resource_plural="capabilities", +) @nested_resource_class_methods( "external_account", operations=["create", "retrieve", "update", "delete", "list"], @@ -54,6 +59,9 @@ def instance_url(self): def persons(self, **params): return self.request("get", self.instance_url() + "/persons", params) + # We are not adding a helper for capabilities here as the Account object already has a + # capabilities property which is a hash and not the sub-list of capabilities. + def reject(self, idempotency_key=None, **params): url = self.instance_url() + "/reject" headers = util.populate_headers(idempotency_key) diff --git a/stripe/api_resources/capability.py b/stripe/api_resources/capability.py new file mode 100644 index 000000000..3a2ca7d7f --- /dev/null +++ b/stripe/api_resources/capability.py @@ -0,0 +1,32 @@ +from __future__ import absolute_import, division, print_function + +from stripe import util +from stripe.api_resources.account import Account +from stripe.api_resources.abstract import UpdateableAPIResource +from stripe.six.moves.urllib.parse import quote_plus + + +class Capability(UpdateableAPIResource): + OBJECT_NAME = "capability" + + def instance_url(self): + token = util.utf8(self.id) + account = util.utf8(self.account) + base = Account.class_url() + acct_extn = quote_plus(account) + extn = quote_plus(token) + return "%s/%s/capabilities/%s" % (base, acct_extn, extn) + + @classmethod + def modify(cls, sid, **params): + raise NotImplementedError( + "Can't update a capability without an account ID. Update a capability using " + "account.modify_capability('acct_123', 'acap_123', params)" + ) + + @classmethod + def retrieve(cls, id, api_key=None, **params): + raise NotImplementedError( + "Can't retrieve a capability without an account ID. Retrieve a capability using " + "account.retrieve_capability('acct_123', 'acap_123')" + ) diff --git a/stripe/util.py b/stripe/util.py index 9c9ab1174..b919c4b01 100644 --- a/stripe/util.py +++ b/stripe/util.py @@ -154,6 +154,7 @@ def load_object_classes(): api_resources.BankAccount.OBJECT_NAME: api_resources.BankAccount, api_resources.BitcoinReceiver.OBJECT_NAME: api_resources.BitcoinReceiver, api_resources.BitcoinTransaction.OBJECT_NAME: api_resources.BitcoinTransaction, + api_resources.Capability.OBJECT_NAME: api_resources.Capability, api_resources.Card.OBJECT_NAME: api_resources.Card, api_resources.Charge.OBJECT_NAME: api_resources.Charge, api_resources.checkout.Session.OBJECT_NAME: api_resources.checkout.Session, diff --git a/tests/api_resources/test_account.py b/tests/api_resources/test_account.py index 771862448..6a797cfee 100644 --- a/tests/api_resources/test_account.py +++ b/tests/api_resources/test_account.py @@ -4,6 +4,7 @@ TEST_RESOURCE_ID = "acct_123" +TEST_CAPABILITY_ID = "acap_123" TEST_EXTERNALACCOUNT_ID = "ba_123" TEST_PERSON_ID = "person_123" @@ -134,6 +135,38 @@ def test_can_call_persons(self, request_mock): assert isinstance(resources.data[0], stripe.Person) +class TestAccountCapabilities(object): + def test_is_listable(self, request_mock): + resources = stripe.Account.list_capabilities(TEST_RESOURCE_ID) + request_mock.assert_requested( + "get", "/v1/accounts/%s/capabilities" % TEST_RESOURCE_ID + ) + assert isinstance(resources.data, list) + assert isinstance(resources.data[0], stripe.Capability) + + def test_is_modifiable(self, request_mock): + resource = stripe.Account.modify_capability( + TEST_RESOURCE_ID, TEST_CAPABILITY_ID, requested=True + ) + request_mock.assert_requested( + "post", + "/v1/accounts/%s/capabilities/%s" + % (TEST_RESOURCE_ID, TEST_CAPABILITY_ID), + ) + assert isinstance(resource, stripe.Capability) + + def test_is_retrievable(self, request_mock): + resource = stripe.Account.retrieve_capability( + TEST_RESOURCE_ID, TEST_CAPABILITY_ID + ) + request_mock.assert_requested( + "get", + "/v1/accounts/%s/capabilities/%s" + % (TEST_RESOURCE_ID, TEST_CAPABILITY_ID), + ) + assert isinstance(resource, stripe.Capability) + + class TestAccountExternalAccounts(object): def test_is_listable(self, request_mock): resources = stripe.Account.list_external_accounts(TEST_RESOURCE_ID) diff --git a/tests/api_resources/test_capability.py b/tests/api_resources/test_capability.py new file mode 100644 index 000000000..776fc2513 --- /dev/null +++ b/tests/api_resources/test_capability.py @@ -0,0 +1,43 @@ +from __future__ import absolute_import, division, print_function + +import pytest + +import stripe + + +TEST_RESOURCE_ID = "acap_123" + + +class TestCapability(object): + def construct_resource(self): + capability_dict = { + "id": TEST_RESOURCE_ID, + "object": "capability", + "account": "acct_123", + } + return stripe.Capability.construct_from( + capability_dict, stripe.api_key + ) + + def test_has_instance_url(self, request_mock): + resource = self.construct_resource() + assert ( + resource.instance_url() + == "/v1/accounts/acct_123/capabilities/%s" % TEST_RESOURCE_ID + ) + + def test_is_not_modifiable(self, request_mock): + with pytest.raises(NotImplementedError): + stripe.Capability.modify(TEST_RESOURCE_ID, requested=True) + + def test_is_not_retrievable(self, request_mock): + with pytest.raises(NotImplementedError): + stripe.Capability.retrieve(TEST_RESOURCE_ID) + + def test_is_saveable(self, request_mock): + resource = self.construct_resource() + resource.requested = True + resource.save() + request_mock.assert_requested( + "post", "/v1/accounts/acct_123/capabilities/%s" % TEST_RESOURCE_ID + ) diff --git a/tests/conftest.py b/tests/conftest.py index 728cb1800..a9a362f23 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,7 +16,7 @@ # When changing this number, don't forget to change it in `.travis.yml` too. -MOCK_MINIMUM_VERSION = "0.54.0" +MOCK_MINIMUM_VERSION = "0.56.0" # Starts stripe-mock if an OpenAPI spec override is found in `openapi/`, and # otherwise fall back to `STRIPE_MOCK_PORT` or 12111.