Skip to content

Commit

Permalink
Merge f3f7453 into 56ef6a8
Browse files Browse the repository at this point in the history
  • Loading branch information
fredpalmer committed Sep 29, 2013
2 parents 56ef6a8 + f3f7453 commit 75e12ba
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 40 deletions.
2 changes: 1 addition & 1 deletion payments/admin.py
Expand Up @@ -27,7 +27,7 @@ def user_search_fields():
try:
# get_field_by_name throws FieldDoesNotExist if the field is not
# present on the model
# pylint: disable-msg=W0212,E1103
# pylint: disable=W0212,E1103
User._meta.get_field_by_name("email")
fields += ["user__email"]
except FieldDoesNotExist:
Expand Down
18 changes: 15 additions & 3 deletions payments/models.py
Expand Up @@ -44,7 +44,7 @@ class StripeObject(models.Model):
stripe_id = models.CharField(max_length=255, unique=True)
created_at = models.DateTimeField(default=timezone.now)

class Meta:
class Meta: # pylint: disable=E0012,C1001
abstract = True


Expand Down Expand Up @@ -207,7 +207,7 @@ def send_signal(self):


class Transfer(StripeObject):
# pylint: disable-msg=C0301
# pylint: disable=C0301
event = models.ForeignKey(Event, related_name="transfers")
amount = models.DecimalField(decimal_places=2, max_digits=9)
status = models.CharField(max_length=25)
Expand Down Expand Up @@ -611,6 +611,18 @@ def is_valid(self):

return True

def delete(self, using=None): # pylint: disable=E1002
"""
Set values to None while deleting the object so that any lingering
references will not show previous values (such as when an Event
signal is triggered after a subscription has been deleted)
"""
super(CurrentSubscription, self).delete(using=using)
self.plan = None
self.status = None
self.quantity = 0
self.amount = 0


class Invoice(models.Model):

Expand All @@ -628,7 +640,7 @@ class Invoice(models.Model):
charge = models.CharField(max_length=50, blank=True)
created_at = models.DateTimeField(default=timezone.now)

class Meta:
class Meta: # pylint: disable=E0012,C1001
ordering = ["-date"]

def retry(self):
Expand Down
38 changes: 19 additions & 19 deletions payments/signals.py
Expand Up @@ -9,40 +9,40 @@
WEBHOOK_SIGNALS = dict([
(hook, Signal(providing_args=["event"]))
for hook in [
"account.updated",
"account.application.deauthorized",
"charge.succeeded",
"charge.failed",
"charge.refunded",
"account.updated",
"charge.dispute.closed",
"charge.dispute.created",
"charge.dispute.updated",
"charge.dispute.closed",
"charge.failed",
"charge.refunded",
"charge.succeeded",
"coupon.created",
"coupon.deleted",
"coupon.updated",
"customer.created",
"customer.updated",
"customer.deleted",
"customer.discount.created",
"customer.discount.deleted",
"customer.discount.updated",
"customer.subscription.created",
"customer.subscription.updated",
"customer.subscription.deleted",
"customer.subscription.trial_will_end",
"customer.discount.created",
"customer.discount.updated",
"customer.discount.deleted",
"customer.subscription.updated",
"customer.updated",
"invoice.created",
"invoice.updated",
"invoice.payment_succeeded",
"invoice.payment_failed",
"invoice.payment_succeeded",
"invoice.updated",
"invoiceitem.created",
"invoiceitem.updated",
"invoiceitem.deleted",
"invoiceitem.updated",
"ping",
"plan.created",
"plan.updated",
"plan.deleted",
"coupon.created",
"coupon.updated",
"coupon.deleted",
"plan.updated",
"transfer.created",
"transfer.updated",
"transfer.failed",
"ping"
"transfer.updated",
]
])
5 changes: 3 additions & 2 deletions payments/tests/test_commands.py
@@ -1,3 +1,4 @@
# pylint: disable=C0301
from django.core import management
from django.test import TestCase

Expand All @@ -15,7 +16,7 @@ def setUp(self):

@patch("stripe.Customer.retrieve")
@patch("stripe.Customer.create")
def test_init_customer_creates_customer(self, CreateMock, RetrieveMock): # pylint: disable=C0301
def test_init_customer_creates_customer(self, CreateMock, RetrieveMock):
CreateMock.return_value.id = "cus_XXXXX"
management.call_command("init_customers")
self.assertEquals(self.user.customer.stripe_id, "cus_XXXXX")
Expand All @@ -39,7 +40,7 @@ def test_plans_create(self, CreateMock):
@patch("payments.models.Customer.sync_current_subscription")
@patch("payments.models.Customer.sync_invoices")
@patch("payments.models.Customer.sync_charges")
def test_sync_customers(self, SyncChargeesMock, SyncInvoicesMock, SyncSubscriptionMock, SyncMock, RetrieveMock): # pylint: disable=C0301
def test_sync_customers(self, SyncChargeesMock, SyncInvoicesMock, SyncSubscriptionMock, SyncMock, RetrieveMock):
user2 = get_user_model().objects.create_user(username="thomas")
get_user_model().objects.create_user(username="altman")
Customer.objects.create(stripe_id="cus_XXXXX", user=self.user)
Expand Down
11 changes: 6 additions & 5 deletions payments/tests/test_customer.py
@@ -1,3 +1,4 @@
# pylint: disable=C0301
import decimal

from django.test import TestCase
Expand Down Expand Up @@ -44,7 +45,7 @@ def test_customer_create_user_only(self, CreateMock, RetrieveMock):
@patch("stripe.Invoice.create")
@patch("stripe.Customer.retrieve")
@patch("stripe.Customer.create")
def test_customer_create_user_with_plan(self, CreateMock, RetrieveMock, PayMock): # pylint: disable=C0301
def test_customer_create_user_with_plan(self, CreateMock, RetrieveMock, PayMock):
self.customer.delete()
stripe_customer = CreateMock()
stripe_customer.active_card = None
Expand All @@ -70,11 +71,11 @@ def test_customer_create_user_with_plan(self, CreateMock, RetrieveMock, PayMock)
self.assertTrue(PayMock.called)
self.assertTrue(customer.current_subscription.plan, "pro")

# @@@ Need to figure out a way to tempmorarily set DEFAULT_PLAN to "entry" for this test # pylint: disable=C0301
# @@@ Need to figure out a way to tempmorarily set DEFAULT_PLAN to "entry" for this test
# @patch("stripe.Invoice.create")
# @patch("stripe.Customer.retrieve")
# @patch("stripe.Customer.create")
# def test_customer_create_user_with_card_default_plan(self, CreateMock, RetrieveMock, PayMock): # pylint: disable=C0301
# def test_customer_create_user_with_card_default_plan(self, CreateMock, RetrieveMock, PayMock):
# self.customer.delete()
# stripe_customer = CreateMock()
# stripe_customer.active_card = None
Expand All @@ -101,7 +102,7 @@ def test_customer_create_user_with_plan(self, CreateMock, RetrieveMock, PayMock)
# self.assertTrue(customer.current_subscription.plan, "entry")

@patch("stripe.Customer.retrieve")
def test_customer_subscribe_with_specified_quantity(self, CustomerRetrieveMock): # pylint: disable=C0301
def test_customer_subscribe_with_specified_quantity(self, CustomerRetrieveMock):
customer = CustomerRetrieveMock()
customer.subscription.plan.id = "entry-monthly"
customer.subscription.current_period_start = 1348360173
Expand All @@ -118,7 +119,7 @@ def test_customer_subscribe_with_specified_quantity(self, CustomerRetrieveMock):
self.assertEqual(kwargs["quantity"], 3)

@patch("stripe.Customer.retrieve")
def test_customer_subscribe_with_callback_quantity(self, CustomerRetrieveMock): # pylint: disable=C0301
def test_customer_subscribe_with_callback_quantity(self, CustomerRetrieveMock):
customer = CustomerRetrieveMock()
customer.subscription.plan.id = "entry-monthly"
customer.subscription.current_period_start = 1348360173
Expand Down
3 changes: 2 additions & 1 deletion payments/tests/test_email.py
@@ -1,3 +1,4 @@
# pylint: disable=C0301
import decimal

from django.core import mail
Expand All @@ -24,7 +25,7 @@ def setUp(self):

@patch("stripe.Charge.retrieve")
@patch("stripe.Charge.create")
def test_email_reciept_renders_amount_properly(self, ChargeMock, RetrieveMock): # pylint: disable=C0301
def test_email_reciept_renders_amount_properly(self, ChargeMock, RetrieveMock):
ChargeMock.return_value.id = "ch_XXXXX"
RetrieveMock.return_value = {
"id": "ch_XXXXXX",
Expand Down
118 changes: 115 additions & 3 deletions payments/tests/test_event.py
@@ -1,13 +1,15 @@
# pylint: disable=C0301
from django.test import TestCase
from django.utils import timezone

from mock import patch
from mock import patch, Mock

from ..models import Customer, Event
from ..models import Customer, Event, CurrentSubscription
from payments.signals import WEBHOOK_SIGNALS
from ..utils import get_user_model


class TestEventMethods(TestCase):

def setUp(self):
User = get_user_model()
self.user = User.objects.create_user(username="testuser")
Expand Down Expand Up @@ -176,3 +178,113 @@ def test_process_customer_deleted(self, CustomerMock):
event.process()
self.assertEquals(event.customer, self.customer)
self.assertEquals(event.customer.user, None)

@staticmethod
def send_signal(customer, kind):
event = Event(customer=customer, kind=kind)
signal = WEBHOOK_SIGNALS.get(kind)
signal.send(sender=Event, event=event)

@staticmethod
def connect_webhook_signal(kind, func, **kwargs):
signal = WEBHOOK_SIGNALS.get(kind)
signal.connect(func, **kwargs)

@staticmethod
def disconnect_webhook_signal(kind, func, **kwargs):
signal = WEBHOOK_SIGNALS.get(kind)
signal.disconnect(func, **kwargs)

@patch("stripe.Customer.retrieve")
def test_customer_subscription_deleted(self, CustomerMock):
"""
Tests to make sure downstream signal handlers do not see stale CurrentSubscription object properties
after a customer.subscription.deleted event occurs. While the delete method is called
on the affected CurrentSubscription object's properties are still accessible (unless the
Customer object for the event gets refreshed before sending the complimentary signal)
"""
kind = "customer.subscription.deleted"
cs = CurrentSubscription(customer=self.customer, quantity=1, start=timezone.now(), amount=0)
cs.save()
customer = Customer.objects.get(pk=self.customer.pk)

# Stripe objects will not have this attribute so we must delete it from the mocked object
del customer.stripe_customer.subscription
self.assertIsNotNone(customer.current_subscription)

# This is the expected format of a customer.subscription.delete message
msg = {
"id": "evt_2eRjeAlnH1XMe8",
"created": 1380317537,
"livemode": True,
"type": kind,
"data": {
"object": {
"id": "su_2ZDdGxJ3EQQc7Q",
"plan": {
"interval": "month",
"name": "xxx",
"amount": 200,
"currency": "usd",
"id": "xxx",
"object": "plan",
"livemode": True,
"interval_count": 1,
"trial_period_days": None
},
"object": "subscription",
"start": 1379111889,
"status": "canceled",
"customer": self.customer.stripe_id,
"cancel_at_period_end": False,
"current_period_start": 1378738246,
"current_period_end": 1381330246,
"ended_at": 1380317537,
"trial_start": None,
"trial_end": None,
"canceled_at": 1380317537,
"quantity": 1,
"application_fee_percent": None
}
},
"object": "event",
"pending_webhooks": 1,
"request": "iar_2eRjQZmn0i3G9M"
}

# Create a test event for the message
test_event = Event.objects.create(
stripe_id=msg["id"],
kind=kind,
livemode=msg["livemode"],
webhook_message=msg,
validated_message=msg,
valid=True,
customer=customer,
)

def signal_handler(sender, event, **kwargs):
# Illustrate and test what signal handlers would experience
self.assertFalse(event.customer.current_subscription.is_valid())
self.assertIsNone(event.customer.current_subscription.plan)
self.assertIsNone(event.customer.current_subscription.status)
self.assertIsNone(event.customer.current_subscription.id)

signal_handler_mock = Mock()
# Let's make the side effect call our real function, the mock is a proxy so we can assert it was called
signal_handler_mock.side_effect = signal_handler
TestEventMethods.connect_webhook_signal(kind, signal_handler_mock, weak=False, sender=Event)
signal_handler_mock.reset_mock()

# Now process the event - at the end of this the signal should get sent
test_event.process()

self.assertFalse(test_event.customer.current_subscription.is_valid())
self.assertIsNone(test_event.customer.current_subscription.plan)
self.assertIsNone(test_event.customer.current_subscription.status)
self.assertIsNone(test_event.customer.current_subscription.id)

# Verify our signal handler was called
self.assertTrue(signal_handler_mock.called)

TestEventMethods.disconnect_webhook_signal(kind, signal_handler_mock, weak=False, sender=Event)
11 changes: 6 additions & 5 deletions payments/tests/test_middleware.py
@@ -1,3 +1,4 @@
# pylint: disable=C0301
import decimal

from django.conf import settings
Expand Down Expand Up @@ -35,7 +36,7 @@ def setUp(self):
user = authenticate(username="patrick", password="eldarion")
login(self.request, user)

def test_authed_user_with_no_customer_redirects_on_non_exempt_url(self): # pylint: disable=C0301
def test_authed_user_with_no_customer_redirects_on_non_exempt_url(self):
self.request.path = "/the/app/"
response = self.middleware.process_request(self.request)
self.assertEqual(response.status_code, 302)
Expand All @@ -44,20 +45,20 @@ def test_authed_user_with_no_customer_redirects_on_non_exempt_url(self): # pyli
reverse(settings.SUBSCRIPTION_REQUIRED_REDIRECT)
)

def test_authed_user_with_no_customer_passes_with_exempt_url(self): # pylint: disable=C0301
def test_authed_user_with_no_customer_passes_with_exempt_url(self):
URLS.append("/accounts/signup/")
self.request.path = "/accounts/signup/"
response = self.middleware.process_request(self.request)
self.assertIsNone(response)

def test_authed_user_with_no_active_subscription_passes_with_exempt_url(self): # pylint: disable=C0301
def test_authed_user_with_no_active_subscription_passes_with_exempt_url(self):
Customer.objects.create(stripe_id="cus_1", user=self.request.user)
URLS.append("/accounts/signup/")
self.request.path = "/accounts/signup/"
response = self.middleware.process_request(self.request)
self.assertIsNone(response)

def test_authed_user_with_no_active_subscription_redirects_on_non_exempt_url(self): # pylint: disable=C0301
def test_authed_user_with_no_active_subscription_redirects_on_non_exempt_url(self):
Customer.objects.create(stripe_id="cus_1", user=self.request.user)
URLS.append("/accounts/signup/")
self.request.path = "/the/app/"
Expand All @@ -68,7 +69,7 @@ def test_authed_user_with_no_active_subscription_redirects_on_non_exempt_url(sel
reverse(settings.SUBSCRIPTION_REQUIRED_REDIRECT)
)

def test_authed_user_with_active_subscription_redirects_on_non_exempt_url(self): # pylint: disable=C0301
def test_authed_user_with_active_subscription_redirects_on_non_exempt_url(self):
customer = Customer.objects.create(
stripe_id="cus_1",
user=self.request.user
Expand Down
1 change: 1 addition & 0 deletions payments/tests/test_views.py
@@ -1,3 +1,4 @@
# pylint: disable=C0301
import decimal
import json

Expand Down
2 changes: 1 addition & 1 deletion payments/utils.py
Expand Up @@ -23,7 +23,7 @@ def convert_tstamp(response, field_name=None):

def get_user_model(): # pragma: no cover
try:
# pylint: disable-msg=E0611
# pylint: disable=E0611
from django.contrib.auth import get_user_model as django_get_user_model
return django_get_user_model()
except ImportError:
Expand Down

0 comments on commit 75e12ba

Please sign in to comment.