コード例 #1
0
class TestBillingPayday(Harness):
    BALANCED_ACCOUNT_URI = '/v1/marketplaces/M123/accounts/A123'

    def setUp(self):
        super(TestBillingPayday, self).setUp()
        self.payday = Payday(self.postgres)

    def test_assert_one_payday(self):
        with assert_raises(AssertionError):
            self.payday.assert_one_payday(None)
        with assert_raises(AssertionError):
            self.payday.assert_one_payday([1, 2])

    @mock.patch('gittip.participant.Participant.get_tips_and_total')
    def test_charge_and_or_transfer_no_tips(self, get_tips_and_total):
        alice = self.make_participant('alice', balance='1.00',
                                      balanced_account_uri=self.BALANCED_ACCOUNT_URI,
                                      is_suspicious=False)
        amount = Decimal('1.00')

        ts_start = self.payday.start()

        tips, total = [], amount

        initial_payday = PaydayModel.query.first().attrs_dict()
        self.payday.charge_and_or_transfer(ts_start, alice.attrs_dict(),
                                           tips, total)
        resulting_payday = PaydayModel.query.first().attrs_dict()

        assert_equals(initial_payday['ntippers'], resulting_payday['ntippers'])
        assert_equals(initial_payday['ntips'], resulting_payday['ntips'])
        assert_equals(initial_payday['nparticipants'] + 1,
                      resulting_payday['nparticipants'])

    @mock.patch('gittip.participant.Participant.get_tips_and_total')
    @mock.patch('gittip.billing.payday.Payday.tip')
    def test_charge_and_or_transfer(self, tip, get_tips_and_total):
        alice = self.make_participant('alice', balance='1.00',
                                      balanced_account_uri=self.BALANCED_ACCOUNT_URI,
                                      is_suspicious=False)
        ts_start = self.payday.start()
        now = datetime.utcnow()
        amount = Decimal('1.00')
        like_a_tip = {'amount': amount, 'tippee': 'mjallday', 'ctime': now,
                      'claimed_time': now}

        # success, success, claimed, failure
        tips = [like_a_tip, like_a_tip, like_a_tip, like_a_tip]
        total = amount

        ts_start = datetime.utcnow()

        return_values = [1, 1, 0, -1]
        return_values.reverse()

        def tip_return_values(*_):
            return return_values.pop()

        tip.side_effect = tip_return_values

        initial_payday = PaydayModel.query.first().attrs_dict()
        self.payday.charge_and_or_transfer(ts_start, alice.attrs_dict(), tips,
                                           total)
        resulting_payday = PaydayModel.query.first().attrs_dict()

        assert_equals(initial_payday['ntippers'] + 1,
                      resulting_payday['ntippers'])
        assert_equals(initial_payday['ntips'] + 2,
                      resulting_payday['ntips'])
        assert_equals(initial_payday['nparticipants'] + 1,
                      resulting_payday['nparticipants'])

    @mock.patch('gittip.participant.Participant.get_tips_and_total')
    @mock.patch('gittip.billing.payday.Payday.charge')
    def test_charge_and_or_transfer_short(self, charge, get_tips_and_total):
        alice = self.make_participant('alice', balance='1.00',
                                      balanced_account_uri=self.BALANCED_ACCOUNT_URI,
                                      is_suspicious=False)
        now = datetime.utcnow()
        amount = Decimal('1.00')
        like_a_tip = {'amount': amount, 'tippee': 'mjallday', 'ctime': now,
                      'claimed_time': now}

        # success, success, claimed, failure
        tips = [like_a_tip, like_a_tip, like_a_tip, like_a_tip]
        get_tips_and_total.return_value = tips, amount

        ts_start = datetime.utcnow()

        # In real-life we wouldn't be able to catch an error as the charge
        # method will swallow any errors and return false. We don't handle this
        # return value within charge_and_or_transfer but instead continue on
        # trying to use the remaining credit in the user's account to payout as
        # many tips as possible.
        #
        # Here we're hacking the system and throwing the exception so execution
        # stops since we're only testing this part of the method. That smells
        # like we need to refactor.

        charge.side_effect = Exception()
        with self.assertRaises(Exception):
            billing.charge_and_or_transfer(ts_start, alice.attrs_dict())
        self.assertTrue(charge.called_with(alice.username,
                                           self.BALANCED_ACCOUNT_URI,
                                           amount))

    @mock.patch('gittip.billing.payday.Payday.transfer')
    @mock.patch('gittip.billing.payday.log')
    def test_tip(self, log, transfer):
        alice = self.make_participant('alice', balance='1.00',
                                      balanced_account_uri=self.BALANCED_ACCOUNT_URI,
                                      is_suspicious=False)
        participant = alice.attrs_dict()
        amount = Decimal('1.00')
        invalid_amount = Decimal('0.00')
        tip = { 'amount': amount
              , 'tippee': alice.username
              , 'claimed_time': utcnow()
               }
        ts_start = utcnow()

        result = self.payday.tip(participant, tip, ts_start)
        assert_equals(result, 1)
        result = transfer.called_with(participant['username'], tip['tippee'],
                                      tip['amount'])
        self.assertTrue(result)

        self.assertTrue(log.called_with('SUCCESS: $1 from mjallday to alice.'))

        # XXX: Should these tests be broken down to a separate class with the
        # common setup factored in to a setUp method.

        # XXX: We should have constants to compare the values to
        # invalid amount
        tip['amount'] = invalid_amount
        result = self.payday.tip(participant, tip, ts_start)
        assert_equals(result, 0)

        tip['amount'] = amount

        # XXX: We should have constants to compare the values to
        # not claimed
        tip['claimed_time'] = None
        result = self.payday.tip(participant, tip, ts_start)
        assert_equals(result, 0)

        # XXX: We should have constants to compare the values to
        # claimed after payday
        tip['claimed_time'] = utcnow()
        result = self.payday.tip(participant, tip, ts_start)
        assert_equals(result, 0)

        ts_start = utcnow()

        # XXX: We should have constants to compare the values to
        # transfer failed
        transfer.return_value = False
        result = self.payday.tip(participant, tip, ts_start)
        assert_equals(result, -1)

    @mock.patch('gittip.billing.payday.log')
    def test_start_zero_out_and_get_participants(self, log):
        self.make_participant('alice', balance=0, claimed_time=None,
                              pending=None,
                              balanced_account_uri=self.BALANCED_ACCOUNT_URI)
        self.make_participant('bob', balance=10, claimed_time=None,
                              pending=1,
                              balanced_account_uri=self.BALANCED_ACCOUNT_URI)
        self.make_participant('carl', balance=10, claimed_time=utcnow(),
                              pending=1,
                              balanced_account_uri=self.BALANCED_ACCOUNT_URI)

        ts_start = self.payday.start()

        self.payday.zero_out_pending(ts_start)
        participants = self.payday.get_participants(ts_start)

        expected_logging_call_args = [
            ('Starting a new payday.'),
            ('Payday started at {}.'.format(ts_start)),
            ('Zeroed out the pending column.'),
            ('Fetched participants.'),
        ]
        expected_logging_call_args.reverse()
        for args, _ in log.call_args_list:
            assert_equals(args[0], expected_logging_call_args.pop())

        log.reset_mock()

        # run a second time, we should see it pick up the existing payday
        second_ts_start = self.payday.start()
        self.payday.zero_out_pending(second_ts_start)
        second_participants = self.payday.get_participants(second_ts_start)

        self.assertEqual(ts_start, second_ts_start)
        participants = list(participants)
        second_participants = list(second_participants)

        # carl is the only valid participant as he has a claimed time
        assert_equals(len(participants), 1)
        assert_equals(participants, second_participants)

        expected_logging_call_args = [
            ('Picking up with an existing payday.'),
            ('Payday started at {}.'.format(second_ts_start)),
            ('Zeroed out the pending column.'),
            ('Fetched participants.')]
        expected_logging_call_args.reverse()
        for args, _ in log.call_args_list:
            assert_equals(args[0], expected_logging_call_args.pop())

    @mock.patch('gittip.billing.payday.log')
    def test_end(self, log):
        self.payday.start()
        self.payday.end()
        self.assertTrue(log.called_with('Finished payday.'))

        # finishing the payday will set the ts_end date on this payday record
        # to now, so this will not return any result
        result = PaydayModel.query.filter(PaydayModel.ts_end > '1970-01-01')\
                                  .count()
        assert_equals(result, 1)

    @mock.patch('gittip.billing.payday.log')
    @mock.patch('gittip.billing.payday.Payday.start')
    @mock.patch('gittip.billing.payday.Payday.payin')
    @mock.patch('gittip.billing.payday.Payday.end')
    def test_payday(self, end, payin, init, log):
        ts_start = utcnow()
        init.return_value = (ts_start,)
        greeting = 'Greetings, program! It\'s PAYDAY!!!!'

        self.payday.run()

        self.assertTrue(log.called_with(greeting))
        self.assertTrue(init.call_count)
        self.assertTrue(payin.called_with(init.return_value))
        self.assertTrue(end.call_count)
コード例 #2
0
class TestPaydayCharge(TestBillingBase):
    STRIPE_CUSTOMER_ID = 'cus_deadbeef'

    def setUp(self):
        super(TestBillingBase, self).setUp()
        self.payday = Payday(self.postgres)

    def get_numbers(self):
        """Return a list of 9 ints:

            nachs
            nach_failing
            ncc_failing
            ncc_missing
            ncharges
            nparticipants
            ntippers
            ntips
            ntransfers

        """
        payday = PaydayModel.query.first()  # There should be a single payday
        attrs = payday.attrs_dict()
        keys = [key for key in sorted(attrs) if key.startswith('n')]
        return [attrs[key] for key in keys]

    def test_charge_without_cc_details_returns_None(self):
        self.make_participant('alice')
        participant = self.postgres.fetchone("SELECT * FROM participants")
        self.payday.start()
        actual = self.payday.charge(participant, Decimal('1.00'))
        assert actual is None, actual

    def test_charge_without_cc_marked_as_failure(self):
        self.make_participant('alice')
        participant = self.postgres.fetchone("SELECT * FROM participants")
        self.payday.start()
        self.payday.charge(participant, Decimal('1.00'))
        actual = self.get_numbers()
        assert_equals(actual, [0, 0, 0, 1, 0, 0, 0, 0, 0])

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_charge_failure_returns_None(self, cob):
        cob.return_value = (Decimal('10.00'), Decimal('0.68'), 'FAILED')
        self.make_participant('bob', last_bill_result="failure",
                              balanced_account_uri=self.balanced_account_uri,
                              stripe_customer_id=self.STRIPE_CUSTOMER_ID,
                              is_suspicious=False)

        participant = self.postgres.fetchone("SELECT * FROM participants")
        self.payday.start()
        actual = self.payday.charge(participant, Decimal('1.00'))
        assert actual is None, actual

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_charge_success_returns_None(self, charge_on_balanced):
        charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'), "")
        self.make_participant('bob', last_bill_result="failure",
                              balanced_account_uri=self.balanced_account_uri,
                              stripe_customer_id=self.STRIPE_CUSTOMER_ID,
                              is_suspicious=False)

        participant = self.postgres.fetchone("SELECT * FROM participants")
        self.payday.start()
        actual = self.payday.charge(participant, Decimal('1.00'))
        assert actual is None, actual

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_charge_success_updates_participant(self, cob):
        cob.return_value = (Decimal('10.00'), Decimal('0.68'), "")
        bob = self.make_participant('bob', last_bill_result="failure",
                                    balanced_account_uri=self.balanced_account_uri,
                                    is_suspicious=False)
        participant = self.postgres.fetchone("SELECT * FROM participants")
        self.payday.start()
        self.payday.charge(participant, Decimal('1.00'))
        expected = {'balance': Decimal('9.32'), 'last_bill_result': ''}
        actual = {'balance': bob.balance,
                  'last_bill_result': bob.last_bill_result}
        assert_equals(actual, expected)

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_payday_moves_money(self, charge_on_balanced):
        charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'), "")
        day_ago = utcnow() - timedelta(days=1)
        bob = self.make_participant('bob', claimed_time=day_ago,
                                    last_bill_result='',
                                    is_suspicious=False)
        carl = self.make_participant('carl', claimed_time=day_ago,
                                     balanced_account_uri=self.balanced_account_uri,
                                     last_bill_result='',
                                     is_suspicious=False)
        Participant(u'carl').set_tip_to('bob', '6.00')  # under $10!
        self.payday.run()
        assert_equals(bob.balance, Decimal('6.00'))
        assert_equals(carl.balance, Decimal('3.32'))

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_payday_doesnt_move_money_from_a_suspicious_account(self, charge_on_balanced):
        charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'), "")
        day_ago = utcnow() - timedelta(days=1)
        bob = self.make_participant('bob', claimed_time=day_ago,
                                    last_bill_result='',
                                    is_suspicious=False)
        carl = self.make_participant('carl', claimed_time=day_ago,
                                     balanced_account_uri=self.balanced_account_uri,
                                     last_bill_result='',
                                     is_suspicious=True)
        Participant(u'carl').set_tip_to('bob', '6.00')  # under $10!
        self.payday.run()
        assert_equals(bob.balance, Decimal('0.00'))
        assert_equals(carl.balance, Decimal('0.00'))

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_payday_doesnt_move_money_to_a_suspicious_account(self, charge_on_balanced):
        charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'), "")
        day_ago = utcnow() - timedelta(days=1)
        bob = self.make_participant('bob', claimed_time=day_ago,
                                    last_bill_result='',
                                    is_suspicious=True)
        carl = self.make_participant('carl', claimed_time=day_ago,
                                     balanced_account_uri=self.balanced_account_uri,
                                     last_bill_result='',
                                     is_suspicious=False)
        Participant(u'carl').set_tip_to('bob', '6.00')  # under $10!
        self.payday.run()
        assert_equals(bob.balance, Decimal('0.00'))
        assert_equals(carl.balance, Decimal('0.00'))
コード例 #3
0
class TestPaydayCharge(TestPaydayBase):
    STRIPE_CUSTOMER_ID = 'cus_deadbeef'

    def setUp(self):
        super(TestBillingBase, self).setUp()
        self.payday = Payday(self.db)

    def get_numbers(self):
        """Return a list of 10 ints:

            nachs
            nach_failing
            nactive
            ncc_failing
            ncc_missing
            ncharges
            npachinko
            nparticipants
            ntippers
            ntips
            ntransfers

        """
        payday = self.fetch_payday()
        keys = [key for key in sorted(payday) if key.startswith('n')]
        return [payday[key] for key in keys]

    def test_charge_without_cc_details_returns_None(self):
        alice = self.make_participant('alice')
        self.payday.start()
        actual = self.payday.charge(alice, Decimal('1.00'))
        assert actual is None

    def test_charge_without_cc_marked_as_failure(self):
        alice = self.make_participant('alice')
        self.payday.start()
        self.payday.charge(alice, Decimal('1.00'))
        actual = self.get_numbers()
        assert actual == [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_charge_failure_returns_None(self, cob):
        cob.return_value = (Decimal('10.00'), Decimal('0.68'), 'FAILED')
        bob = self.make_participant('bob', last_bill_result="failure",
                                    balanced_account_uri=self.balanced_account_uri,
                                    stripe_customer_id=self.STRIPE_CUSTOMER_ID,
                                    is_suspicious=False)

        self.payday.start()
        actual = self.payday.charge(bob, Decimal('1.00'))
        assert actual is None

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_charge_success_returns_None(self, charge_on_balanced):
        charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'), "")
        bob = self.make_participant('bob', last_bill_result="failure",
                                    balanced_account_uri=self.balanced_account_uri,
                                    stripe_customer_id=self.STRIPE_CUSTOMER_ID,
                                    is_suspicious=False)

        self.payday.start()
        actual = self.payday.charge(bob, Decimal('1.00'))
        assert actual is None

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_charge_success_updates_participant(self, cob):
        cob.return_value = (Decimal('10.00'), Decimal('0.68'), "")
        bob = self.make_participant('bob', last_bill_result="failure",
                                    balanced_account_uri=self.balanced_account_uri,
                                    is_suspicious=False)
        self.payday.start()
        self.payday.charge(bob, Decimal('1.00'))

        bob = Participant.from_username('bob')
        expected = {'balance': Decimal('9.32'), 'last_bill_result': ''}
        actual = {'balance': bob.balance,
                  'last_bill_result': bob.last_bill_result}
        assert actual == expected

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_payday_moves_money(self, charge_on_balanced):
        charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'), "")
        day_ago = utcnow() - timedelta(days=1)
        bob = self.make_participant('bob', claimed_time=day_ago,
                                    last_bill_result='',
                                    is_suspicious=False)
        carl = self.make_participant('carl', claimed_time=day_ago,
                                     balanced_account_uri=self.balanced_account_uri,
                                     last_bill_result='',
                                     is_suspicious=False)
        carl.set_tip_to('bob', '6.00')  # under $10!
        self.payday.run()

        bob = Participant.from_username('bob')
        carl = Participant.from_username('carl')

        assert bob.balance == Decimal('6.00')
        assert carl.balance == Decimal('3.32')

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_payday_doesnt_move_money_from_a_suspicious_account(self, charge_on_balanced):
        charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'), "")
        day_ago = utcnow() - timedelta(days=1)
        bob = self.make_participant('bob', claimed_time=day_ago,
                                    last_bill_result='',
                                    is_suspicious=False)
        carl = self.make_participant('carl', claimed_time=day_ago,
                                     balanced_account_uri=self.balanced_account_uri,
                                     last_bill_result='',
                                     is_suspicious=True)
        carl.set_tip_to('bob', '6.00')  # under $10!
        self.payday.run()

        bob = Participant.from_username('bob')
        carl = Participant.from_username('carl')

        assert bob.balance == Decimal('0.00')
        assert carl.balance == Decimal('0.00')

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_payday_doesnt_move_money_to_a_suspicious_account(self, charge_on_balanced):
        charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'), "")
        day_ago = utcnow() - timedelta(days=1)
        bob = self.make_participant('bob', claimed_time=day_ago,
                                    last_bill_result='',
                                    is_suspicious=True)
        carl = self.make_participant('carl', claimed_time=day_ago,
                                     balanced_account_uri=self.balanced_account_uri,
                                     last_bill_result='',
                                     is_suspicious=False)
        carl.set_tip_to('bob', '6.00')  # under $10!
        self.payday.run()

        bob = Participant.from_username('bob')
        carl = Participant.from_username('carl')

        assert bob.balance == Decimal('0.00')
        assert carl.balance == Decimal('0.00')
コード例 #4
0
class TestBillingPayday(TestPaydayBase):
    BALANCED_ACCOUNT_URI = '/v1/marketplaces/M123/accounts/A123'

    def setUp(self):
        super(TestBillingPayday, self).setUp()
        self.payday = Payday(self.db)

    def test_move_pending_to_balance_for_teams_does_so(self):
        self.make_participant('A', number='plural', balance=2, pending=3)
        self.payday.move_pending_to_balance_for_teams()
        actual = self.db.one("SELECT balance FROM participants WHERE username='******'")
        assert actual == 5

    def test_move_pending_to_balance_for_teams_ignores_new_teams(self):
        # See https://github.com/gittip/www.gittip.com/issues/1684
        self.make_participant('A', number='plural', balance=0, pending=None)
        self.payday.move_pending_to_balance_for_teams()
        actual = self.db.one("SELECT balance FROM participants WHERE username='******'")
        assert actual == 0

    @mock.patch('gittip.models.participant.Participant.get_tips_and_total')
    def test_charge_and_or_transfer_no_tips(self, get_tips_and_total):
        self.db.run("""

            UPDATE participants
               SET balance=1
                 , balanced_account_uri=%s
                 , is_suspicious=False
             WHERE username='******'

        """, (self.BALANCED_ACCOUNT_URI,))

        amount = Decimal('1.00')

        ts_start = self.payday.start()

        tips, total = [], amount

        initial_payday = self.fetch_payday()
        self.payday.charge_and_or_transfer(ts_start, self.alice, tips, total)
        resulting_payday = self.fetch_payday()

        assert initial_payday['ntippers'] == resulting_payday['ntippers']
        assert initial_payday['ntips'] == resulting_payday['ntips']
        assert initial_payday['nparticipants'] + 1 == resulting_payday['nparticipants']

    @mock.patch('gittip.models.participant.Participant.get_tips_and_total')
    @mock.patch('gittip.billing.payday.Payday.tip')
    def test_charge_and_or_transfer(self, tip, get_tips_and_total):
        self.db.run("""

            UPDATE participants
               SET balance=1
                 , balanced_account_uri=%s
                 , is_suspicious=False
             WHERE username='******'

        """, (self.BALANCED_ACCOUNT_URI,))

        ts_start = self.payday.start()
        now = datetime.utcnow()
        amount = Decimal('1.00')
        like_a_tip = {'amount': amount, 'tippee': 'mjallday', 'ctime': now,
                      'claimed_time': now}

        # success, success, claimed, failure
        tips = [like_a_tip, like_a_tip, like_a_tip, like_a_tip]
        total = amount

        ts_start = datetime.utcnow()

        return_values = [1, 1, 0, -1]
        return_values.reverse()

        def tip_return_values(*_):
            return return_values.pop()

        tip.side_effect = tip_return_values

        initial_payday = self.fetch_payday()
        self.payday.charge_and_or_transfer(ts_start, self.alice, tips, total)
        resulting_payday = self.fetch_payday()

        assert initial_payday['ntippers'] + 1 == resulting_payday['ntippers']
        assert initial_payday['ntips'] + 2 == resulting_payday['ntips']
        assert initial_payday['nparticipants'] + 1 == resulting_payday['nparticipants']

    @mock.patch('gittip.models.participant.Participant.get_tips_and_total')
    @mock.patch('gittip.billing.payday.Payday.charge')
    def test_charge_and_or_transfer_short(self, charge, get_tips_and_total):
        self.db.run("""

            UPDATE participants
               SET balance=1
                 , balanced_account_uri=%s
                 , is_suspicious=False
             WHERE username='******'

        """, (self.BALANCED_ACCOUNT_URI,))

        now = datetime.utcnow()
        amount = Decimal('1.00')
        like_a_tip = {'amount': amount, 'tippee': 'mjallday', 'ctime': now,
                      'claimed_time': now}

        # success, success, claimed, failure
        tips = [like_a_tip, like_a_tip, like_a_tip, like_a_tip]
        get_tips_and_total.return_value = tips, amount

        ts_start = datetime.utcnow()

        # In real-life we wouldn't be able to catch an error as the charge
        # method will swallow any errors and return false. We don't handle this
        # return value within charge_and_or_transfer but instead continue on
        # trying to use the remaining credit in the user's account to payout as
        # many tips as possible.
        #
        # Here we're hacking the system and throwing the exception so execution
        # stops since we're only testing this part of the method. That smells
        # like we need to refactor.

        charge.side_effect = Exception()
        with self.assertRaises(Exception):
            billing.charge_and_or_transfer(ts_start, self.alice)
        assert charge.called_with(self.alice.username,
                                  self.BALANCED_ACCOUNT_URI,
                                  amount)

    @mock.patch('gittip.billing.payday.Payday.transfer')
    @mock.patch('gittip.billing.payday.log')
    def test_tip(self, log, transfer):
        self.db.run("""

            UPDATE participants
               SET balance=1
                 , balanced_account_uri=%s
                 , is_suspicious=False
             WHERE username='******'

        """, (self.BALANCED_ACCOUNT_URI,))
        amount = Decimal('1.00')
        invalid_amount = Decimal('0.00')
        tip = { 'amount': amount
              , 'tippee': self.alice.username
              , 'claimed_time': utcnow()
               }
        ts_start = utcnow()

        result = self.payday.tip(self.alice, tip, ts_start)
        assert result == 1
        result = transfer.called_with(self.alice.username, tip['tippee'],
                                      tip['amount'])
        assert result

        assert log.called_with('SUCCESS: $1 from mjallday to alice.')

        # XXX: Should these tests be broken down to a separate class with the
        # common setup factored in to a setUp method.

        # XXX: We should have constants to compare the values to
        # invalid amount
        tip['amount'] = invalid_amount
        result = self.payday.tip(self.alice, tip, ts_start)
        assert result == 0

        tip['amount'] = amount

        # XXX: We should have constants to compare the values to
        # not claimed
        tip['claimed_time'] = None
        result = self.payday.tip(self.alice, tip, ts_start)
        assert result == 0

        # XXX: We should have constants to compare the values to
        # claimed after payday
        tip['claimed_time'] = utcnow()
        result = self.payday.tip(self.alice, tip, ts_start)
        assert result == 0

        ts_start = utcnow()

        # XXX: We should have constants to compare the values to
        # transfer failed
        transfer.return_value = False
        result = self.payday.tip(self.alice, tip, ts_start)
        assert result == -1

    @mock.patch('gittip.billing.payday.log')
    def test_start_zero_out_and_get_participants(self, log):
        self.make_participant('bob', balance=10, claimed_time=None,
                              pending=1,
                              balanced_account_uri=self.BALANCED_ACCOUNT_URI)
        self.make_participant('carl', balance=10, claimed_time=utcnow(),
                              pending=1,
                              balanced_account_uri=self.BALANCED_ACCOUNT_URI)
        self.db.run("""

            UPDATE participants
               SET balance=0
                 , claimed_time=null
                 , pending=null
                 , balanced_account_uri=%s
             WHERE username='******'

        """, (self.BALANCED_ACCOUNT_URI,))

        ts_start = self.payday.start()

        self.payday.zero_out_pending(ts_start)
        participants = self.payday.get_participants(ts_start)

        expected_logging_call_args = [
            ('Starting a new payday.'),
            ('Payday started at {}.'.format(ts_start)),
            ('Zeroed out the pending column.'),
            ('Fetched participants.'),
        ]
        expected_logging_call_args.reverse()
        for args, _ in log.call_args_list:
            assert args[0] == expected_logging_call_args.pop()

        log.reset_mock()

        # run a second time, we should see it pick up the existing payday
        second_ts_start = self.payday.start()
        self.payday.zero_out_pending(second_ts_start)
        second_participants = self.payday.get_participants(second_ts_start)

        assert ts_start == second_ts_start
        participants = list(participants)
        second_participants = list(second_participants)

        # carl is the only valid participant as he has a claimed time
        assert len(participants) == 1
        assert participants == second_participants

        expected_logging_call_args = [
            ('Picking up with an existing payday.'),
            ('Payday started at {}.'.format(second_ts_start)),
            ('Zeroed out the pending column.'),
            ('Fetched participants.')]
        expected_logging_call_args.reverse()
        for args, _ in log.call_args_list:
            assert args[0] == expected_logging_call_args.pop()

    @mock.patch('gittip.billing.payday.log')
    def test_end(self, log):
        self.payday.start()
        self.payday.end()
        assert log.called_with('Finished payday.')

        # finishing the payday will set the ts_end date on this payday record
        # to now, so this will not return any result
        result = self.db.one("SELECT count(*) FROM paydays "
                             "WHERE ts_end > '1970-01-01'")
        assert result == 1

    @mock.patch('gittip.billing.payday.log')
    @mock.patch('gittip.billing.payday.Payday.start')
    @mock.patch('gittip.billing.payday.Payday.payin')
    @mock.patch('gittip.billing.payday.Payday.end')
    def test_payday(self, end, payin, init, log):
        ts_start = utcnow()
        init.return_value = (ts_start,)
        greeting = 'Greetings, program! It\'s PAYDAY!!!!'

        self.payday.run()

        assert log.called_with(greeting)
        assert init.call_count
        assert payin.called_with(init.return_value)
        assert end.call_count
コード例 #5
0
class TestBillingPayday(TestPaydayBase):
    BALANCED_ACCOUNT_URI = '/v1/marketplaces/M123/accounts/A123'

    def setUp(self):
        super(TestBillingPayday, self).setUp()
        self.payday = Payday(self.db)

    @mock.patch('gittip.models.participant.Participant.get_tips_and_total')
    def test_charge_and_or_transfer_no_tips(self, get_tips_and_total):
        self.db.run(
            """

            UPDATE participants
               SET balance=1
                 , balanced_account_uri=%s
                 , is_suspicious=False
             WHERE username='******'

        """, (self.BALANCED_ACCOUNT_URI, ))

        amount = Decimal('1.00')

        ts_start = self.payday.start()

        tips, total = [], amount

        initial_payday = self.fetch_payday()
        self.payday.charge_and_or_transfer(ts_start, self.alice, tips, total)
        resulting_payday = self.fetch_payday()

        assert_equals(initial_payday['ntippers'], resulting_payday['ntippers'])
        assert_equals(initial_payday['ntips'], resulting_payday['ntips'])
        assert_equals(initial_payday['nparticipants'] + 1,
                      resulting_payday['nparticipants'])

    @mock.patch('gittip.models.participant.Participant.get_tips_and_total')
    @mock.patch('gittip.billing.payday.Payday.tip')
    def test_charge_and_or_transfer(self, tip, get_tips_and_total):
        self.db.run(
            """

            UPDATE participants
               SET balance=1
                 , balanced_account_uri=%s
                 , is_suspicious=False
             WHERE username='******'

        """, (self.BALANCED_ACCOUNT_URI, ))

        ts_start = self.payday.start()
        now = datetime.utcnow()
        amount = Decimal('1.00')
        like_a_tip = {
            'amount': amount,
            'tippee': 'mjallday',
            'ctime': now,
            'claimed_time': now
        }

        # success, success, claimed, failure
        tips = [like_a_tip, like_a_tip, like_a_tip, like_a_tip]
        total = amount

        ts_start = datetime.utcnow()

        return_values = [1, 1, 0, -1]
        return_values.reverse()

        def tip_return_values(*_):
            return return_values.pop()

        tip.side_effect = tip_return_values

        initial_payday = self.fetch_payday()
        self.payday.charge_and_or_transfer(ts_start, self.alice, tips, total)
        resulting_payday = self.fetch_payday()

        assert_equals(initial_payday['ntippers'] + 1,
                      resulting_payday['ntippers'])
        assert_equals(initial_payday['ntips'] + 2, resulting_payday['ntips'])
        assert_equals(initial_payday['nparticipants'] + 1,
                      resulting_payday['nparticipants'])

    @mock.patch('gittip.models.participant.Participant.get_tips_and_total')
    @mock.patch('gittip.billing.payday.Payday.charge')
    def test_charge_and_or_transfer_short(self, charge, get_tips_and_total):
        self.db.run(
            """

            UPDATE participants
               SET balance=1
                 , balanced_account_uri=%s
                 , is_suspicious=False
             WHERE username='******'

        """, (self.BALANCED_ACCOUNT_URI, ))

        now = datetime.utcnow()
        amount = Decimal('1.00')
        like_a_tip = {
            'amount': amount,
            'tippee': 'mjallday',
            'ctime': now,
            'claimed_time': now
        }

        # success, success, claimed, failure
        tips = [like_a_tip, like_a_tip, like_a_tip, like_a_tip]
        get_tips_and_total.return_value = tips, amount

        ts_start = datetime.utcnow()

        # In real-life we wouldn't be able to catch an error as the charge
        # method will swallow any errors and return false. We don't handle this
        # return value within charge_and_or_transfer but instead continue on
        # trying to use the remaining credit in the user's account to payout as
        # many tips as possible.
        #
        # Here we're hacking the system and throwing the exception so execution
        # stops since we're only testing this part of the method. That smells
        # like we need to refactor.

        charge.side_effect = Exception()
        with self.assertRaises(Exception):
            billing.charge_and_or_transfer(ts_start, self.alice)
        self.assertTrue(
            charge.called_with(self.alice.username, self.BALANCED_ACCOUNT_URI,
                               amount))

    @mock.patch('gittip.billing.payday.Payday.transfer')
    @mock.patch('gittip.billing.payday.log')
    def test_tip(self, log, transfer):
        self.db.run(
            """

            UPDATE participants
               SET balance=1
                 , balanced_account_uri=%s
                 , is_suspicious=False
             WHERE username='******'

        """, (self.BALANCED_ACCOUNT_URI, ))
        amount = Decimal('1.00')
        invalid_amount = Decimal('0.00')
        tip = {
            'amount': amount,
            'tippee': self.alice.username,
            'claimed_time': utcnow()
        }
        ts_start = utcnow()

        result = self.payday.tip(self.alice, tip, ts_start)
        assert_equals(result, 1)
        result = transfer.called_with(self.alice.username, tip['tippee'],
                                      tip['amount'])
        self.assertTrue(result)

        self.assertTrue(log.called_with('SUCCESS: $1 from mjallday to alice.'))

        # XXX: Should these tests be broken down to a separate class with the
        # common setup factored in to a setUp method.

        # XXX: We should have constants to compare the values to
        # invalid amount
        tip['amount'] = invalid_amount
        result = self.payday.tip(self.alice, tip, ts_start)
        assert_equals(result, 0)

        tip['amount'] = amount

        # XXX: We should have constants to compare the values to
        # not claimed
        tip['claimed_time'] = None
        result = self.payday.tip(self.alice, tip, ts_start)
        assert_equals(result, 0)

        # XXX: We should have constants to compare the values to
        # claimed after payday
        tip['claimed_time'] = utcnow()
        result = self.payday.tip(self.alice, tip, ts_start)
        assert_equals(result, 0)

        ts_start = utcnow()

        # XXX: We should have constants to compare the values to
        # transfer failed
        transfer.return_value = False
        result = self.payday.tip(self.alice, tip, ts_start)
        assert_equals(result, -1)

    @mock.patch('gittip.billing.payday.log')
    def test_start_zero_out_and_get_participants(self, log):
        self.make_participant('bob',
                              balance=10,
                              claimed_time=None,
                              pending=1,
                              balanced_account_uri=self.BALANCED_ACCOUNT_URI)
        self.make_participant('carl',
                              balance=10,
                              claimed_time=utcnow(),
                              pending=1,
                              balanced_account_uri=self.BALANCED_ACCOUNT_URI)
        self.db.run(
            """

            UPDATE participants
               SET balance=0
                 , claimed_time=null
                 , pending=null
                 , balanced_account_uri=%s
             WHERE username='******'

        """, (self.BALANCED_ACCOUNT_URI, ))

        ts_start = self.payday.start()

        self.payday.zero_out_pending(ts_start)
        participants = self.payday.get_participants(ts_start)

        expected_logging_call_args = [
            ('Starting a new payday.'),
            ('Payday started at {}.'.format(ts_start)),
            ('Zeroed out the pending column.'),
            ('Fetched participants.'),
        ]
        expected_logging_call_args.reverse()
        for args, _ in log.call_args_list:
            assert_equals(args[0], expected_logging_call_args.pop())

        log.reset_mock()

        # run a second time, we should see it pick up the existing payday
        second_ts_start = self.payday.start()
        self.payday.zero_out_pending(second_ts_start)
        second_participants = self.payday.get_participants(second_ts_start)

        self.assertEqual(ts_start, second_ts_start)
        participants = list(participants)
        second_participants = list(second_participants)

        # carl is the only valid participant as he has a claimed time
        assert_equals(len(participants), 1)
        assert_equals(participants, second_participants)

        expected_logging_call_args = [
            ('Picking up with an existing payday.'),
            ('Payday started at {}.'.format(second_ts_start)),
            ('Zeroed out the pending column.'), ('Fetched participants.')
        ]
        expected_logging_call_args.reverse()
        for args, _ in log.call_args_list:
            assert_equals(args[0], expected_logging_call_args.pop())

    @mock.patch('gittip.billing.payday.log')
    def test_end(self, log):
        self.payday.start()
        self.payday.end()
        self.assertTrue(log.called_with('Finished payday.'))

        # finishing the payday will set the ts_end date on this payday record
        # to now, so this will not return any result
        result = self.db.one("SELECT count(*) FROM paydays "
                             "WHERE ts_end > '1970-01-01'")
        assert_equals(result, 1)

    @mock.patch('gittip.billing.payday.log')
    @mock.patch('gittip.billing.payday.Payday.start')
    @mock.patch('gittip.billing.payday.Payday.payin')
    @mock.patch('gittip.billing.payday.Payday.end')
    def test_payday(self, end, payin, init, log):
        ts_start = utcnow()
        init.return_value = (ts_start, )
        greeting = 'Greetings, program! It\'s PAYDAY!!!!'

        self.payday.run()

        self.assertTrue(log.called_with(greeting))
        self.assertTrue(init.call_count)
        self.assertTrue(payin.called_with(init.return_value))
        self.assertTrue(end.call_count)
コード例 #6
0
class TestPaydayCharge(TestPaydayBase):
    STRIPE_CUSTOMER_ID = 'cus_deadbeef'

    def setUp(self):
        super(TestBillingBase, self).setUp()
        self.payday = Payday(self.db)

    def get_numbers(self):
        """Return a list of 9 ints:

            nachs
            nach_failing
            ncc_failing
            ncc_missing
            ncharges
            npachinko
            nparticipants
            ntippers
            ntips
            ntransfers

        """
        payday = self.fetch_payday()
        keys = [key for key in sorted(payday) if key.startswith('n')]
        return [payday[key] for key in keys]

    def test_charge_without_cc_details_returns_None(self):
        alice = self.make_participant('alice')
        self.payday.start()
        actual = self.payday.charge(alice, Decimal('1.00'))
        assert actual is None, actual

    def test_charge_without_cc_marked_as_failure(self):
        alice = self.make_participant('alice')
        self.payday.start()
        self.payday.charge(alice, Decimal('1.00'))
        actual = self.get_numbers()
        assert_equals(actual, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0])

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_charge_failure_returns_None(self, cob):
        cob.return_value = (Decimal('10.00'), Decimal('0.68'), 'FAILED')
        bob = self.make_participant(
            'bob',
            last_bill_result="failure",
            balanced_account_uri=self.balanced_account_uri,
            stripe_customer_id=self.STRIPE_CUSTOMER_ID,
            is_suspicious=False)

        self.payday.start()
        actual = self.payday.charge(bob, Decimal('1.00'))
        assert actual is None, actual

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_charge_success_returns_None(self, charge_on_balanced):
        charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'),
                                           "")
        bob = self.make_participant(
            'bob',
            last_bill_result="failure",
            balanced_account_uri=self.balanced_account_uri,
            stripe_customer_id=self.STRIPE_CUSTOMER_ID,
            is_suspicious=False)

        self.payday.start()
        actual = self.payday.charge(bob, Decimal('1.00'))
        assert actual is None, actual

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_charge_success_updates_participant(self, cob):
        cob.return_value = (Decimal('10.00'), Decimal('0.68'), "")
        bob = self.make_participant(
            'bob',
            last_bill_result="failure",
            balanced_account_uri=self.balanced_account_uri,
            is_suspicious=False)
        self.payday.start()
        self.payday.charge(bob, Decimal('1.00'))

        bob = Participant.from_username('bob')
        expected = {'balance': Decimal('9.32'), 'last_bill_result': ''}
        actual = {
            'balance': bob.balance,
            'last_bill_result': bob.last_bill_result
        }
        assert_equals(actual, expected)

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_payday_moves_money(self, charge_on_balanced):
        charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'),
                                           "")
        day_ago = utcnow() - timedelta(days=1)
        bob = self.make_participant('bob',
                                    claimed_time=day_ago,
                                    last_bill_result='',
                                    is_suspicious=False)
        carl = self.make_participant(
            'carl',
            claimed_time=day_ago,
            balanced_account_uri=self.balanced_account_uri,
            last_bill_result='',
            is_suspicious=False)
        carl.set_tip_to('bob', '6.00')  # under $10!
        self.payday.run()

        bob = Participant.from_username('bob')
        carl = Participant.from_username('carl')

        assert_equals(bob.balance, Decimal('6.00'))
        assert_equals(carl.balance, Decimal('3.32'))

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_payday_doesnt_move_money_from_a_suspicious_account(
            self, charge_on_balanced):
        charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'),
                                           "")
        day_ago = utcnow() - timedelta(days=1)
        bob = self.make_participant('bob',
                                    claimed_time=day_ago,
                                    last_bill_result='',
                                    is_suspicious=False)
        carl = self.make_participant(
            'carl',
            claimed_time=day_ago,
            balanced_account_uri=self.balanced_account_uri,
            last_bill_result='',
            is_suspicious=True)
        carl.set_tip_to('bob', '6.00')  # under $10!
        self.payday.run()

        bob = Participant.from_username('bob')
        carl = Participant.from_username('carl')

        assert_equals(bob.balance, Decimal('0.00'))
        assert_equals(carl.balance, Decimal('0.00'))

    @mock.patch('gittip.billing.payday.Payday.charge_on_balanced')
    def test_payday_doesnt_move_money_to_a_suspicious_account(
            self, charge_on_balanced):
        charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'),
                                           "")
        day_ago = utcnow() - timedelta(days=1)
        bob = self.make_participant('bob',
                                    claimed_time=day_ago,
                                    last_bill_result='',
                                    is_suspicious=True)
        carl = self.make_participant(
            'carl',
            claimed_time=day_ago,
            balanced_account_uri=self.balanced_account_uri,
            last_bill_result='',
            is_suspicious=False)
        carl.set_tip_to('bob', '6.00')  # under $10!
        self.payday.run()

        bob = Participant.from_username('bob')
        carl = Participant.from_username('carl')

        assert_equals(bob.balance, Decimal('0.00'))
        assert_equals(carl.balance, Decimal('0.00'))