Skip to content

Commit

Permalink
Add support for the Capability resource and APIs (#566)
Browse files Browse the repository at this point in the history
  • Loading branch information
remi-stripe authored and ob-stripe committed May 14, 2019
1 parent 25b21ce commit a1df6d1
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions stripe/api_resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions stripe/api_resources/abstract/nested_resource_class_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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:
Expand Down
8 changes: 8 additions & 0 deletions stripe/api_resources/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down Expand Up @@ -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)
Expand Down
32 changes: 32 additions & 0 deletions stripe/api_resources/capability.py
Original file line number Diff line number Diff line change
@@ -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')"
)
1 change: 1 addition & 0 deletions stripe/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
33 changes: 33 additions & 0 deletions tests/api_resources/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


TEST_RESOURCE_ID = "acct_123"
TEST_CAPABILITY_ID = "acap_123"
TEST_EXTERNALACCOUNT_ID = "ba_123"
TEST_PERSON_ID = "person_123"

Expand Down Expand Up @@ -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)
Expand Down
43 changes: 43 additions & 0 deletions tests/api_resources/test_capability.py
Original file line number Diff line number Diff line change
@@ -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
)
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit a1df6d1

Please sign in to comment.