Beispiel #1
0
    def test_run_billing_processor_logic_when_no_errors(self) -> None:
        second_realm = Realm.objects.create(string_id='second', name='second')
        entry1 = self.add_log_entry(realm=second_realm)
        realm_processor = BillingProcessor.objects.create(
            realm=second_realm, log_row=entry1, state=BillingProcessor.DONE)
        entry2 = self.add_log_entry()
        # global processor
        processor = BillingProcessor.objects.create(
            log_row=entry2, state=BillingProcessor.DONE)

        # Test nothing to process
        # test nothing changes, for global processor
        self.assertFalse(run_billing_processor_one_step(processor))
        self.assertEqual(2, BillingProcessor.objects.count())
        # test realm processor gets deleted
        self.assertFalse(run_billing_processor_one_step(realm_processor))
        self.assertEqual(1, BillingProcessor.objects.count())
        self.assertEqual(1, BillingProcessor.objects.filter(realm=None).count())

        # Test something to process
        processor.state = BillingProcessor.STARTED
        processor.save()
        realm_processor = BillingProcessor.objects.create(
            realm=second_realm, log_row=entry1, state=BillingProcessor.STARTED)
        Customer.objects.create(realm=get_realm('zulip'), stripe_customer_id='cust_1')
        Customer.objects.create(realm=second_realm, stripe_customer_id='cust_2')
        with patch('corporate.lib.stripe.do_adjust_subscription_quantity'):
            # test return values
            self.assertTrue(run_billing_processor_one_step(processor))
            self.assertTrue(run_billing_processor_one_step(realm_processor))
        # test no processors get added or deleted
        self.assertEqual(2, BillingProcessor.objects.count())
Beispiel #2
0
    def test_run_billing_processor_with_card_error(self, mock_billing_logger_error: Mock) -> None:
        second_realm = Realm.objects.create(string_id='second', name='second')
        entry1 = self.add_log_entry(realm=second_realm)
        # global processor
        processor = BillingProcessor.objects.create(
            log_row=entry1, state=BillingProcessor.STARTED)
        Customer.objects.create(realm=second_realm, stripe_customer_id='cust_2')

        # card error on global processor should create a new realm processor
        with patch('corporate.lib.stripe.do_adjust_subscription_quantity',
                   side_effect=stripe.error.CardError('message', 'param', 'code', json_body={})):
            self.assertTrue(run_billing_processor_one_step(processor))
        self.assertEqual(2, BillingProcessor.objects.count())
        self.assertTrue(BillingProcessor.objects.filter(
            realm=None, log_row=entry1, state=BillingProcessor.SKIPPED).exists())
        self.assertTrue(BillingProcessor.objects.filter(
            realm=second_realm, log_row=entry1, state=BillingProcessor.STALLED).exists())
        mock_billing_logger_error.assert_called()

        # card error on realm processor should change state to STALLED
        realm_processor = BillingProcessor.objects.filter(realm=second_realm).first()
        realm_processor.state = BillingProcessor.STARTED
        realm_processor.save()
        with patch('corporate.lib.stripe.do_adjust_subscription_quantity',
                   side_effect=stripe.error.CardError('message', 'param', 'code', json_body={})):
            self.assertTrue(run_billing_processor_one_step(realm_processor))
        self.assertEqual(2, BillingProcessor.objects.count())
        self.assertTrue(BillingProcessor.objects.filter(
            realm=second_realm, log_row=entry1, state=BillingProcessor.STALLED).exists())
        mock_billing_logger_error.assert_called()
Beispiel #3
0
 def test_run_billing_processor_with_uncaught_error(
         self, mock_billing_logger_error: mock.Mock) -> None:
     # This tests three different things:
     # * That run_billing_processor_one_step passes through exceptions that
     #   are not StripeCardError
     # * That process_billing_log_entry catches StripeErrors and re-raises them as BillingErrors
     # * That processor.state=STARTED for non-StripeCardError exceptions
     entry1 = self.add_log_entry()
     entry2 = self.add_log_entry()
     processor = BillingProcessor.objects.create(
         log_row=entry1, state=BillingProcessor.DONE)
     Customer.objects.create(realm=get_realm('zulip'),
                             stripe_customer_id='cust_1')
     with mock.patch('corporate.lib.stripe.do_adjust_subscription_quantity',
                     side_effect=stripe.error.StripeError('message',
                                                          'param',
                                                          'code',
                                                          json_body={})):
         with self.assertRaises(BillingError):
             run_billing_processor_one_step(processor)
     mock_billing_logger_error.assert_called()
     # check processor.state is STARTED
     self.assertTrue(
         BillingProcessor.objects.filter(
             log_row=entry2, state=BillingProcessor.STARTED).exists())
Beispiel #4
0
    def test_run_billing_processor_with_card_error(self, mock_billing_logger_error: Mock) -> None:
        second_realm = Realm.objects.create(string_id='second', name='second')
        entry1 = self.add_log_entry(realm=second_realm)
        # global processor
        processor = BillingProcessor.objects.create(
            log_row=entry1, state=BillingProcessor.STARTED)
        Customer.objects.create(realm=second_realm, stripe_customer_id='cust_2')

        # card error on global processor should create a new realm processor
        with patch('corporate.lib.stripe.do_adjust_subscription_quantity',
                   side_effect=stripe.error.CardError('message', 'param', 'code', json_body={})):
            self.assertTrue(run_billing_processor_one_step(processor))
        self.assertEqual(2, BillingProcessor.objects.count())
        self.assertTrue(BillingProcessor.objects.filter(
            realm=None, log_row=entry1, state=BillingProcessor.SKIPPED).exists())
        self.assertTrue(BillingProcessor.objects.filter(
            realm=second_realm, log_row=entry1, state=BillingProcessor.STALLED).exists())
        mock_billing_logger_error.assert_called()

        # card error on realm processor should change state to STALLED
        realm_processor = BillingProcessor.objects.filter(realm=second_realm).first()
        realm_processor.state = BillingProcessor.STARTED
        realm_processor.save()
        with patch('corporate.lib.stripe.do_adjust_subscription_quantity',
                   side_effect=stripe.error.CardError('message', 'param', 'code', json_body={})):
            self.assertTrue(run_billing_processor_one_step(realm_processor))
        self.assertEqual(2, BillingProcessor.objects.count())
        self.assertTrue(BillingProcessor.objects.filter(
            realm=second_realm, log_row=entry1, state=BillingProcessor.STALLED).exists())
        mock_billing_logger_error.assert_called()
Beispiel #5
0
    def test_run_billing_processor_logic_when_no_errors(self) -> None:
        second_realm = Realm.objects.create(string_id='second', name='second')
        entry1 = self.add_log_entry(realm=second_realm)
        realm_processor = BillingProcessor.objects.create(
            realm=second_realm, log_row=entry1, state=BillingProcessor.DONE)
        entry2 = self.add_log_entry()
        # global processor
        processor = BillingProcessor.objects.create(
            log_row=entry2, state=BillingProcessor.DONE)

        # Test nothing to process
        # test nothing changes, for global processor
        self.assertFalse(run_billing_processor_one_step(processor))
        self.assertEqual(2, BillingProcessor.objects.count())
        # test realm processor gets deleted
        self.assertFalse(run_billing_processor_one_step(realm_processor))
        self.assertEqual(1, BillingProcessor.objects.count())
        self.assertEqual(1, BillingProcessor.objects.filter(realm=None).count())

        # Test something to process
        processor.state = BillingProcessor.STARTED
        processor.save()
        realm_processor = BillingProcessor.objects.create(
            realm=second_realm, log_row=entry1, state=BillingProcessor.STARTED)
        Customer.objects.create(realm=get_realm('zulip'), stripe_customer_id='cust_1')
        Customer.objects.create(realm=second_realm, stripe_customer_id='cust_2')
        with patch('corporate.lib.stripe.do_adjust_subscription_quantity'):
            # test return values
            self.assertTrue(run_billing_processor_one_step(processor))
            self.assertTrue(run_billing_processor_one_step(realm_processor))
        # test no processors get added or deleted
        self.assertEqual(2, BillingProcessor.objects.count())
Beispiel #6
0
 def check_billing_processor_update(event_type: str, quantity: int) -> None:
     def check_subscription_save(subscription: stripe.Subscription, idempotency_key: str) -> None:
         self.assertEqual(subscription.quantity, quantity)
         log_row = RealmAuditLog.objects.filter(
             event_type=event_type, requires_billing_update=True).order_by('-id').first()
         self.assertEqual(idempotency_key, 'process_billing_log_entry:%s' % (log_row.id,))
         self.assertEqual(subscription.proration_date, datetime_to_timestamp(log_row.event_time))
     with patch.object(stripe.Subscription, 'save', autospec=True,
                       side_effect=check_subscription_save):
         run_billing_processor_one_step(processor)
Beispiel #7
0
 def check_billing_processor_update(event_type: str, quantity: int) -> None:
     def check_subscription_save(subscription: stripe.Subscription, idempotency_key: str) -> None:
         self.assertEqual(subscription.quantity, quantity)
         log_row = RealmAuditLog.objects.filter(
             event_type=event_type, requires_billing_update=True).order_by('-id').first()
         self.assertEqual(idempotency_key, 'process_billing_log_entry:%s' % (log_row.id,))
         self.assertEqual(subscription.proration_date, datetime_to_timestamp(log_row.event_time))
     with patch.object(stripe.Subscription, 'save', autospec=True,
                       side_effect=check_subscription_save):
         run_billing_processor_one_step(processor)
Beispiel #8
0
 def test_run_billing_processor_with_uncaught_error(self, mock_billing_logger_error: Mock) -> None:
     # This tests three different things:
     # * That run_billing_processor_one_step passes through exceptions that
     #   are not StripeCardError
     # * That process_billing_log_entry catches StripeErrors and re-raises them as BillingErrors
     # * That processor.state=STARTED for non-StripeCardError exceptions
     entry1 = self.add_log_entry()
     entry2 = self.add_log_entry()
     processor = BillingProcessor.objects.create(
         log_row=entry1, state=BillingProcessor.DONE)
     Customer.objects.create(realm=get_realm('zulip'), stripe_customer_id='cust_1')
     with patch('corporate.lib.stripe.do_adjust_subscription_quantity',
                side_effect=stripe.error.StripeError('message', json_body={})):
         with self.assertRaises(BillingError):
             run_billing_processor_one_step(processor)
     mock_billing_logger_error.assert_called()
     # check processor.state is STARTED
     self.assertTrue(BillingProcessor.objects.filter(
         log_row=entry2, state=BillingProcessor.STARTED).exists())
    def handle(self, *args: Any, **options: Any) -> None:
        if not settings.BILLING_PROCESSOR_ENABLED:
            sleep_forever()

        with lockfile("/tmp/zulip_billing_processor.lockfile"):
            while True:
                for processor in BillingProcessor.objects.exclude(
                        state=BillingProcessor.STALLED):
                    try:
                        entry_processed = run_billing_processor_one_step(processor)
                    except StripeConnectionError:
                        time.sleep(5*60)
                    # Less load on the db during times of activity
                    # and more responsiveness when the load is low
                    if entry_processed:
                        time.sleep(10)
                    else:
                        time.sleep(2)