def test_card_hold_error(self, Customer, fch):
     self.janet.set_tip_to(self.homer, 17)
     Customer.side_effect = Foobar
     fch.return_value = {}
     Payday.start().payin()
     payday = self.fetch_payday()
     assert payday['ncc_failing'] == 1
Beispiel #2
0
def payday():

    # Wire things up.
    # ===============

    env = wireup.env()
    db = wireup.db(env)

    wireup.billing(env)
    wireup.nanswers(env)


    # Lazily import the billing module.
    # =================================
    # This dodges a problem where db in billing is None if we import it from
    # gittip before calling wireup.billing.

    from gittip.billing.exchanges import sync_with_balanced
    from gittip.billing.payday import Payday

    try:
        with db.get_cursor() as cursor:
            sync_with_balanced(cursor)
        Payday.start().run()
    except KeyboardInterrupt:
        pass
    except:
        import aspen
        import traceback
        aspen.log(traceback.format_exc())
    def test_paydays_json_gives_paydays(self):
        Payday.start()
        self.make_participant("alice")

        response = self.client.GET("/about/paydays.json")
        paydays = json.loads(response.body)
        assert paydays[0]['ntippers'] == 0
 def test_payout_ach_error(self, ach_credit):
     self.make_participant('alice', claimed_time='now', is_suspicious=False,
                           balance=20, balanced_customer_href='foo',
                           last_ach_result='')
     ach_credit.return_value = 'some error'
     Payday.start().payout()
     payday = self.fetch_payday()
     assert payday['nach_failing'] == 1
    def test_update_receiving_amounts_updates_receiving_amounts(self):
        A = self.make_participant('A')
        B = self.make_participant('B', claimed_time='now', last_bill_result='')
        B.set_tip_to(A, D('10.00'), update_tippee=False)
        assert Participant.from_username('A').receiving == 0

        Payday.start().update_receiving_amounts()
        assert Participant.from_username('A').receiving == 10
 def test_payin_doesnt_make_null_transfers(self):
     alice = self.make_participant('alice', claimed_time='now')
     alice.set_tip_to(self.homer, 1)
     alice.set_tip_to(self.homer, 0)
     a_team = self.make_participant('a_team', claimed_time='now', number='plural')
     a_team.add_member(alice)
     Payday.start().payin()
     transfers0 = self.db.all("SELECT * FROM transfers WHERE amount = 0")
     assert not transfers0
Beispiel #7
0
def test_prep_hit_basically_works():
    payday = Payday(gittip.db)
    actual = payday._prep_hit(Decimal('20.00'))
    expected = ( 2110
               , u'Charging %s 2110 cents ($20.00 + $1.10 fee = $21.10) on %s '
                 u'... '
               , Decimal('21.10')
               , Decimal('1.10')
                )
    assert actual == expected, actual
def test_prep_hit_full_in_rounded_case():
    payday = Payday(gittip.db)
    actual = payday._prep_hit(Decimal('5.00'))
    expected = ( 1000
               , u'Charging %s 1000 cents ($9.32 [rounded up from $5.00] + '
                 u'$0.68 fee = $10.00) on %s ... '
               , Decimal('10.00')
               , Decimal('0.68')
                )
    assert actual == expected, actual
def test_prep_hit_basically_works():
    payday = Payday(gittip.db)
    actual = payday._prep_hit(Decimal('20.00'))
    expected = ( 2110
               , u'Charging %s 2110 cents ($20.00 + $1.10 fee = $21.10) on %s '
                 u'... '
               , Decimal('21.10')
               , Decimal('1.10')
                )
    assert actual == expected, actual
    def test_payday_moves_money(self, fch):
        self.janet.set_tip_to(self.homer, '6.00')  # under $10!
        fch.return_value = {}
        Payday.start().run()

        janet = Participant.from_username('janet')
        homer = Participant.from_username('homer')

        assert homer.balance == D('6.00')
        assert janet.balance == D('3.41')
Beispiel #11
0
def test_prep_hit_full_in_rounded_case():
    payday = Payday(gittip.db)
    actual = payday._prep_hit(Decimal('5.00'))
    expected = ( 1000
               , u'Charging %s 1000 cents ($9.32 [rounded up from $5.00] + '
                 u'$0.68 fee = $10.00) on %s ... '
               , Decimal('10.00')
               , Decimal('0.68')
                )
    assert actual == expected, actual
 def test_payin_dumps_transfers_for_debugging(self, cch, fch):
     self.janet.set_tip_to(self.homer, 10)
     fake_hold = mock.MagicMock()
     fake_hold.amount = 1500
     fch.return_value = {self.janet.id: fake_hold}
     cch.side_effect = Foobar
     open = mock.MagicMock()
     with mock.patch.dict(__builtins__, {'open': open}):
         with self.assertRaises(Foobar):
             Payday.start().payin()
     assert open.call_count == 1
Beispiel #13
0
    def test_stats_description_accurate_outside_of_payday(self, mock_datetime):
        """Test stats page outside of the payday running"""
        a_monday = datetime(2012, 8, 6, 11, 00, 01)
        mock_datetime.utcnow.return_value = a_monday

        payday = Payday(self.postgres)
        payday.start()

        body = self.get_stats_page()
        assert "is ready for <b>this Thursday</b>" in body, body
        payday.end()
Beispiel #14
0
def test_stats_description_accurate_outside_of_payday(mock_datetime):
    """Test stats page outside of the payday running"""
    with testing.load() as context:
        a_monday = datetime(2012, 8, 6, 12, 00, 01)
        mock_datetime.utcnow.return_value = a_monday

        pd = Payday(context.db)
        pd.start()

        body = get_stats_page()
        assert "is ready for <b>this Thursday</b>" in body, body
        pd.end()
 def test_payin_cancels_uncaptured_holds(self, log):
     self.janet.set_tip_to(self.homer, 42)
     alice = self.make_participant('alice', claimed_time='now',
                                   is_suspicious=False)
     self.make_exchange('bill', 50, 0, alice)
     alice.set_tip_to(self.janet, 50)
     Payday.start().payin()
     assert log.call_args_list[-3][0] == ("Captured 0 card holds.",)
     assert log.call_args_list[-2][0] == ("Canceled 1 card holds.",)
     assert Participant.from_id(alice.id).balance == 0
     assert Participant.from_id(self.janet.id).balance == 8
     assert Participant.from_id(self.homer.id).balance == 42
def prep(amount):
    """Given a dollar amount as a string, return a 3-tuple.

    The return tuple is like the one returned from _prep_hit, but with the
    second value, a log message, removed.

    """
    typecheck(amount, unicode)
    payday = Payday(gittip.db)
    out = list(payday._prep_hit(Decimal(amount)))
    out = [out[0]] + out[2:]
    return tuple(out)
 def test_transfer_takes_doesnt_make_negative_transfers(self, fch):
     hold = balanced.CardHold(amount=1500, meta={'participant_id': self.janet.id})
     hold.capture = lambda *a, **kw: None
     hold.save = lambda *a, **kw: None
     fch.return_value = {self.janet.id: hold}
     self.janet.update_number('plural')
     self.janet.set_tip_to(self.homer, 10)
     self.janet.add_member(self.david)
     Payday.start().payin()
     assert Participant.from_id(self.david.id).balance == 0
     assert Participant.from_id(self.homer.id).balance == 10
     assert Participant.from_id(self.janet.id).balance == 0
Beispiel #18
0
def prep(amount):
    """Given a dollar amount as a string, return a 3-tuple.

    The return tuple is like the one returned from _prep_hit, but with the
    second value, a log message, removed.

    """
    typecheck(amount, unicode)
    payday = Payday(gittip.db)
    out = list(payday._prep_hit(Decimal(amount)))
    out = [out[0]] + out[2:]
    return tuple(out)
    def test_stats_description_accurate_outside_of_payday(self, utcnow):
        """Test stats page outside of the payday running"""

        a_monday = DateTime(2012, 8, 6, 11, 00, 01)
        utcnow.return_value = a_monday

        self.client.hydrate_website()

        payday = Payday(self.db)
        payday.start()

        body = self.get_stats_page()
        assert "is ready for <b>this Thursday</b>" in body, body
        payday.end()
Beispiel #20
0
    def test_stats_description_accurate_outside_of_payday(self, mock_datetime):
        """Test stats page outside of the payday running"""
        self.clear_paydays()
        a_monday = datetime(2012, 8, 6, 12, 00, 01)
        mock_datetime.utcnow.return_value = a_monday

        db = wireup.db()
        wireup.billing()
        pd = Payday(db)
        pd.start()

        body = self.get_stats_page()
        self.assertTrue("is ready for <b>this Friday</b>" in body)
        pd.end()
    def test_iter_payday_events(self):
        Payday.start().run()
        team = self.make_participant('team', number='plural', claimed_time='now')
        alice = self.make_participant('alice', claimed_time='now')
        self.make_exchange('bill', 10000, 0, team)
        self.make_exchange('bill', 10000, 0, alice)
        self.make_exchange('bill', -5000, 0, alice)
        self.db.run("""
            UPDATE transfers
               SET timestamp = "timestamp" - interval '1 month'
        """)
        bob = self.make_participant('bob', claimed_time='now')
        carl = self.make_participant('carl', claimed_time='now')
        team.add_member(bob)
        team.set_take_for(bob, Decimal('1.00'), team)
        alice.set_tip_to(bob, Decimal('5.00'))

        assert bob.balance == 0
        for i in range(2):
            with patch.object(Payday, 'fetch_card_holds') as fch:
                fch.return_value = {}
                Payday.start().run()
            self.db.run("""
                UPDATE paydays
                   SET ts_start = ts_start - interval '1 week'
                     , ts_end = ts_end - interval '1 week';
                UPDATE transfers
                   SET timestamp = "timestamp" - interval '1 week';
            """)
        bob = Participant.from_id(bob.id)
        assert bob.balance == 12

        Payday().start()
        events = list(iter_payday_events(self.db, bob))
        assert len(events) == 8
        assert events[0]['kind'] == 'day-open'
        assert events[0]['payday_number'] == 2
        assert events[1]['balance'] == 12
        assert events[-1]['kind'] == 'day-close'
        assert events[-1]['balance'] == '0.00'

        alice = Participant.from_id(alice.id)
        assert alice.balance == 4990
        events = list(iter_payday_events(self.db, alice))
        assert len(events) == 10

        carl = Participant.from_id(carl.id)
        assert carl.balance == 0
        events = list(iter_payday_events(self.db, carl))
        assert len(events) == 0
    def test_payday_doesnt_move_money_to_a_suspicious_account(self, fch):
        self.db.run("""
            UPDATE participants
               SET is_suspicious = true
             WHERE username = '******'
        """)
        self.janet.set_tip_to(self.homer, '6.00')  # under $10!
        fch.return_value = {}
        Payday.start().run()

        janet = Participant.from_username('janet')
        homer = Participant.from_username('homer')

        assert janet.balance == D('0.00')
        assert homer.balance == D('0.00')
    def test_start_prepare(self, log):
        self.clear_tables()
        self.make_participant('bob', balance=10, claimed_time=None)
        self.make_participant('carl', balance=10, claimed_time='now')

        payday = Payday.start()
        ts_start = payday.ts_start

        get_participants = lambda c: c.all("SELECT * FROM payday_participants")

        with self.db.get_cursor() as cursor:
            payday.prepare(cursor, ts_start)
            participants = get_participants(cursor)

        expected_logging_call_args = [
            ('Starting a new payday.'),
            ('Payday started at {}.'.format(ts_start)),
            ('Prepared the DB.'),
        ]
        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
        payday = Payday.start()
        second_ts_start = payday.ts_start
        with self.db.get_cursor() as cursor:
            payday.prepare(cursor, second_ts_start)
            second_participants = get_participants(cursor)

        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)),
            ('Prepared the DB.'),
        ]
        expected_logging_call_args.reverse()
        for args, _ in log.call_args_list:
            assert args[0] == expected_logging_call_args.pop()
 def test_mark_charge_failed(self):
     payday = Payday.start()
     before = self.fetch_payday()
     with self.db.get_cursor() as cursor:
         payday.mark_charge_failed(cursor)
     after = self.fetch_payday()
     assert after['ncc_failing'] == before['ncc_failing'] + 1
 def test_fetch_card_holds_handles_extra_holds(self, cancel, CardHold):
     fake_hold = mock.MagicMock()
     fake_hold.meta = {'participant_id': 0}
     fake_hold.save = mock.MagicMock()
     CardHold.query.filter.return_value = [fake_hold]
     for attr, state in (('failure_reason', 'failed'),
                         ('voided_at', 'cancelled'),
                         ('debit_href', 'captured')):
         holds = Payday.fetch_card_holds(set())
         assert fake_hold.meta['state'] == state
         fake_hold.save.assert_called_with()
         assert len(holds) == 0
         setattr(fake_hold, attr, None)
     holds = Payday.fetch_card_holds(set())
     cancel.assert_called_with(fake_hold)
     assert len(holds) == 0
    def test_transfer_takes(self):
        a_team = self.make_participant('a_team', claimed_time='now', number='plural', balance=20)
        alice = self.make_participant('alice', claimed_time='now')
        a_team.add_member(alice)
        a_team.add_member(self.make_participant('bob', claimed_time='now'))
        a_team.set_take_for(alice, D('1.00'), alice)

        payday = Payday.start()

        # Test that payday ignores takes set after it started
        a_team.set_take_for(alice, D('2.00'), alice)

        # Run the transfer multiple times to make sure we ignore takes that
        # have already been processed
        for i in range(3):
            with self.db.get_cursor() as cursor:
                payday.prepare(cursor, payday.ts_start)
                payday.transfer_takes(cursor, payday.ts_start)
                payday.update_balances(cursor)

        participants = self.db.all("SELECT username, balance FROM participants")

        for p in participants:
            if p.username == 'a_team':
                assert p.balance == D('18.99')
            elif p.username == 'alice':
                assert p.balance == D('1.00')
            elif p.username == 'bob':
                assert p.balance == D('0.01')
            else:
                assert p.balance == 0
def start_payday(*data):
    context = load(*data)
    context.payday = Payday(gittip.db)
    ts_start = context.payday.start()
    context.payday.zero_out_pending(ts_start)
    context.ts_start = ts_start
    return context
Beispiel #28
0
    def test_stats_description_accurate_during_payday_run(self, mock_datetime):
        """Test that stats page takes running payday into account.

        This test was originally written to expose the fix required for
        https://github.com/gittip/www.gittip.com/issues/92.
        """
        a_thursday = datetime(2012, 8, 9, 11, 00, 01)
        mock_datetime.utcnow.return_value = a_thursday

        wireup.billing()
        payday = Payday(self.postgres)
        payday.start()

        body = self.get_stats_page()
        assert "is changing hands <b>right now!</b>" in body, body
        payday.end()
 def setUp(self):
     super(Harness, self).setUp()
     self.postgres = wireup.db()
     self.payday = Payday(self.postgres)
     self.payday.start()
     self.tipper = self.make_participant('lgtest')
     self.balanced_account_uri = '/v1/marketplaces/M123/accounts/A123'
Beispiel #30
0
def payday():

    # Wire things up.
    # ===============
    # Manually override max db connections so that we only have one connection.
    # Our db access is serialized right now anyway, and with only one
    # connection it's easier to trust changes to statement_timeout. The point
    # here is that we want to turn off statement_timeout for payday.

    env = wireup.env()
    env.database_maxconn = 1
    db = wireup.db(env)
    db.run("SET statement_timeout = 0")

    wireup.billing(env)
    wireup.nanswers(env)


    # Lazily import the billing module.
    # =================================
    # This dodges a problem where db in billing is None if we import it from
    # gittip before calling wireup.billing.

    from gittip.billing.payday import Payday

    try:
        Payday(db).run()
    except KeyboardInterrupt:
        pass
    except:
        import aspen
        import traceback
        aspen.log(traceback.format_exc())
Beispiel #31
0
    def test_stats_description_accurate_outside_of_payday(self):
        """Test stats page outside of the payday running"""

        # Hydrating a website requires a functioning datetime module.
        self.client.hydrate_website()

        a_monday = datetime.datetime(2012, 8, 6, 11, 00, 01)
        with patch.object(datetime, 'datetime') as mock_datetime:
            mock_datetime.utcnow.return_value = a_monday

            payday = Payday(self.db)
            payday.start()

            body = self.get_stats_page()
            assert "is ready for <b>this Thursday</b>" in body, body
            payday.end()
Beispiel #32
0
def test_payday_doesnt_move_money_from_a_suspicious_account(charge_on_balanced):
    charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'), None)
    tips = testing.setup_tips(('buz', 'bar', '6.00', True, True))  # under $10!
    with testing.load(*tips) as context:
        Payday(context.db).run()
        actual = context.diff(compact=True)
        assert actual == {"paydays": [1,0,0]}, actual
Beispiel #33
0
def test_stats_description_accurate_during_payday_run(mock_datetime):
    """Test that stats page takes running payday into account.

    This test was originally written to expose the fix required for
    https://github.com/whit537/www.gittip.com/issues/92.
    """
    with testing.load() as context:
        a_thursday = datetime(2012, 8, 9, 12, 00, 01)
        mock_datetime.utcnow.return_value = a_thursday

        wireup.billing()
        pd = Payday(context.db)
        pd.start()

        body = get_stats_page()
        assert "is changing hands <b>right now!</b>" in body, body
        pd.end()
Beispiel #34
0
    def test_stats_description_accurate_during_payday_run(self, mock_datetime):
        """Test that stats page takes running payday into account.

        This test was originally written to expose the fix required for
        https://github.com/whit537/www.gittip.com/issues/92.
        """
        self.clear_paydays()
        a_friday = datetime(2012, 8, 10, 12, 00, 01)
        mock_datetime.utcnow.return_value = a_friday

        db = wireup.db()
        wireup.billing()
        pd = Payday(db)
        pd.start()

        body = self.get_stats_page()
        self.assertTrue("is changing hands <b>right now!</b>" in body)
        pd.end()
Beispiel #35
0
def test_payday_moves_money(charge_on_balanced):
    charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'), None)
    tips = testing.setup_tips(('buz', 'bar', '6.00', True))  # under $10!
    with testing.load(*tips) as context:
        Payday(context.db).run()
        expected = [ {"id": "buz", "balance": Decimal('3.32')}
                   , {"id": "bar", "balance": Decimal('6.00')}
                    ]
        actual = context.diff()['participants']['updates']
        assert actual == expected, actual
    def test_stats_description_accurate_during_payday_run(self, utcnow):
        """Test that stats page takes running payday into account.

        This test was originally written to expose the fix required for
        https://github.com/gittip/www.gittip.com/issues/92.
        """
        a_thursday = DateTime(2012, 8, 9, 11, 00, 01)
        utcnow.return_value = a_thursday

        self.client.hydrate_website()

        env = wireup.env()
        wireup.billing(env)
        payday = Payday(self.db)
        payday.start()

        body = self.get_stats_page()
        assert "is changing hands <b>right now!</b>" in body, body
        payday.end()
Beispiel #37
0
    def test_stats_description_accurate_during_payday_run(self):
        """Test that stats page takes running payday into account.

        This test was originally written to expose the fix required for
        https://github.com/gittip/www.gittip.com/issues/92.
        """

        # Hydrating a website requires a functioning datetime module.
        self.client.hydrate_website()

        a_thursday = datetime.datetime(2012, 8, 9, 11, 00, 01)
        with patch.object(datetime, 'datetime') as mock_datetime:
            mock_datetime.utcnow.return_value = a_thursday

            wireup.billing()
            payday = Payday(self.db)
            payday.start()

            body = self.get_stats_page()
            assert "is changing hands <b>right now!</b>" in body, body
            payday.end()
Beispiel #38
0
def test_payday_does_stuff(charge_on_balanced):
    charge_on_balanced.return_value = (Decimal('10.00'), Decimal('0.68'), None)
    tips = testing.setup_tips(('buz', 'bar', '6.00', True))  # under $10!
    with testing.load(*tips) as context:
        Payday(context.db).run()
        expected = { 'exchanges': [1, 0, 0]
                   , 'participants': [0, 2, 0]
                   , 'paydays': [1, 0, 0]
                   , 'transfers': [1, 0, 0]
                    }
        actual = context.diff(compact=True)
        assert actual == expected, actual
Beispiel #39
0
    def test_stats_description_accurate_outside_of_payday(self, mock_datetime):
        """Test stats page outside of the payday running"""
        a_monday = datetime(2012, 8, 6, 11, 00, 01)
        mock_datetime.utcnow.return_value = a_monday

        payday = Payday(self.postgres)
        payday.start()

        body = self.get_stats_page()
        assert "is ready for <b>this Thursday</b>" in body, body
        payday.end()
Beispiel #40
0
def payday():
    db = wireup.db()
    wireup.billing()

    # Lazily import the billing module.
    # =================================
    # This dodges a problem where db in billing is None if we import it from
    # gittip before calling wireup.billing.

    from gittip.billing.payday import Payday

    try:
        Payday(db).run()
    except KeyboardInterrupt:
        pass
    except:
        import aspen
        import traceback
        aspen.log(traceback.format_exc())
Beispiel #41
0
    def test_stats_description_accurate_outside_of_payday(self, utcnow):
        """Test stats page outside of the payday running"""

        a_monday = DateTime(2012, 8, 6, 11, 00, 01)
        utcnow.return_value = a_monday

        self.client.hydrate_website()

        payday = Payday(self.db)
        payday.start()

        body = self.get_stats_page()
        assert "is ready for <b>this Thursday</b>" in body, body
        payday.end()
Beispiel #42
0
    def test_stats_description_accurate_outside_of_payday(self):
        """Test stats page outside of the payday running"""

        # Hydrating a website requires a functioning datetime module.
        self.client.hydrate_website()

        a_monday = datetime.datetime(2012, 8, 6, 11, 00, 01)
        with patch.object(datetime, 'datetime') as mock_datetime:
            mock_datetime.utcnow.return_value = a_monday

            payday = Payday(self.db)
            payday.start()

            body = self.get_stats_page()
            assert "is ready for <b>this Thursday</b>" in body, body
            payday.end()
Beispiel #43
0
    def test_stats_description_accurate_during_payday_run(self, mock_datetime):
        """Test that stats page takes running payday into account.

        This test was originally written to expose the fix required for
        https://github.com/gittip/www.gittip.com/issues/92.
        """
        a_thursday = datetime(2012, 8, 9, 11, 00, 01)
        mock_datetime.utcnow.return_value = a_thursday

        wireup.billing()
        payday = Payday(self.postgres)
        payday.start()

        body = self.get_stats_page()
        assert "is changing hands <b>right now!</b>" in body, body
        payday.end()
Beispiel #44
0
    def test_stats_description_accurate_during_payday_run(self, utcnow):
        """Test that stats page takes running payday into account.

        This test was originally written to expose the fix required for
        https://github.com/gittip/www.gittip.com/issues/92.
        """
        a_thursday = DateTime(2012, 8, 9, 11, 00, 01)
        utcnow.return_value = a_thursday

        self.client.hydrate_website()

        env = wireup.env()
        wireup.billing(env)
        payday = Payday(self.db)
        payday.start()

        body = self.get_stats_page()
        assert "is changing hands <b>right now!</b>" in body, body
        payday.end()
Beispiel #45
0
    def test_stats_description_accurate_during_payday_run(self):
        """Test that stats page takes running payday into account.

        This test was originally written to expose the fix required for
        https://github.com/gittip/www.gittip.com/issues/92.
        """

        # Hydrating a website requires a functioning datetime module.
        self.client.hydrate_website()

        a_thursday = datetime.datetime(2012, 8, 9, 11, 00, 01)
        with patch.object(datetime, 'datetime') as mock_datetime:
            mock_datetime.utcnow.return_value = a_thursday

            wireup.billing()
            payday = Payday(self.db)
            payday.start()

            body = self.get_stats_page()
            assert "is changing hands <b>right now!</b>" in body, body
            payday.end()
Beispiel #46
0
class TestPachinko(Harness):
    def setUp(self):
        Harness.setUp(self)
        self.payday = Payday(self.db)

    def test_get_participants_gets_participants(self):
        a_team = self.make_participant('a_team',
                                       claimed_time='now',
                                       number='plural',
                                       balance=20)
        a_team.add_member(self.make_participant('alice', claimed_time='now'))
        a_team.add_member(self.make_participant('bob', claimed_time='now'))

        ts_start = self.payday.start()

        actual = [p.username for p in self.payday.get_participants(ts_start)]
        expected = ['a_team', 'alice', 'bob']
        assert actual == expected

    def test_pachinko_pachinkos(self):
        a_team = self.make_participant('a_team',
                                       claimed_time='now',
                                       number='plural',
                                       balance=20,
                                       pending=0)
        a_team.add_member(
            self.make_participant('alice',
                                  claimed_time='now',
                                  balance=0,
                                  pending=0))
        a_team.add_member(
            self.make_participant('bob',
                                  claimed_time='now',
                                  balance=0,
                                  pending=0))

        ts_start = self.payday.start()

        participants = self.payday.genparticipants(ts_start, ts_start)
        self.payday.pachinko(ts_start, participants)
 def setUp(self):
     super(TestBillingBase, self).setUp()
     self.payday = Payday(self.postgres)
 def setUpClass(cls):
     Harness.setUpClass()
     cls.payday = Payday(mock.Mock())  # Mock out the DB connection
class TestBillingTransfer(Harness):
    def setUp(self):
        super(Harness, self).setUp()
        self.payday = Payday(self.postgres)
        self.payday.start()
        self.tipper = self.make_participant('lgtest')
        self.balanced_account_uri = '/v1/marketplaces/M123/accounts/A123'

    def test_transfer(self):
        amount = Decimal('1.00')
        sender = self.make_participant('test_transfer_sender',
                                       pending=0,
                                       balance=1)
        recipient = self.make_participant('test_transfer_recipient',
                                          pending=0,
                                          balance=1)

        result = self.payday.transfer(sender.username, recipient.username,
                                      amount)
        assert_equals(result, True)

        # no balance remaining for a second transfer
        result = self.payday.transfer(sender.username, recipient.username,
                                      amount)
        assert_equals(result, False)

    def test_debit_participant(self):
        amount = Decimal('1.00')
        subject = self.make_participant('test_debit_participant',
                                        pending=0,
                                        balance=1)

        initial_amount = subject.balance

        with self.postgres.get_connection() as connection:
            cursor = connection.cursor()

            self.payday.debit_participant(cursor, subject.username, amount)
            connection.commit()

        self.session.refresh(subject)

        expected = initial_amount - amount
        actual = subject.balance
        assert_equals(actual, expected)

        # this will fail because not enough balance
        with self.postgres.get_connection() as conn:
            cur = conn.cursor()

            with self.assertRaises(IntegrityError):
                self.payday.debit_participant(cur, subject.username, amount)
            conn.commit()

    def test_skim_credit(self):
        actual = skim_credit(Decimal('10.00'))
        assert actual == (Decimal('10.00'), Decimal('0.00')), actual

    def test_credit_participant(self):
        amount = Decimal('1.00')
        subject = self.make_participant('test_credit_participant',
                                        pending=0,
                                        balance=1)

        initial_amount = subject.pending

        with self.postgres.get_connection() as conn:
            cur = conn.cursor()
            self.payday.credit_participant(cur, subject.username, amount)
            conn.commit()

        self.session.refresh(subject)

        expected = initial_amount + amount
        actual = subject.pending
        assert_equals(actual, expected)

    def test_record_transfer(self):
        from gittip.models import Transfer
        amount = Decimal('1.00')
        subjects = ['jim', 'kate', 'bob']

        for subject in subjects:
            self.make_participant(subject, balance=1, pending=0)

        with self.postgres.get_connection() as conn:
            cur = conn.cursor()

            # Tip 'jim' twice
            for recipient in ['jim'] + subjects:
                self.payday.record_transfer(cur, self.tipper.username,
                                            recipient, amount)
            conn.commit()

        for subject in subjects:
            # 'jim' is tipped twice
            expected = amount * 2 if subject == 'jim' else amount
            transfers = Transfer.query.filter_by(tippee=subject).all()
            actual = sum(tip.amount for tip in transfers)
            assert_equals(actual, expected)

    def test_record_transfer_invalid_participant(self):
        amount = Decimal('1.00')

        with self.postgres.get_connection() as conn:
            cur = conn.cursor()
            with assert_raises(IntegrityError):
                self.payday.record_transfer(cur, 'idontexist', 'nori', amount)
            conn.commit()

    def test_mark_transfer(self):
        amount = Decimal('1.00')

        # Forces a load with current state in dict
        before_transfer = PaydayModel.query.first().attrs_dict()

        with self.postgres.get_connection() as conn:
            cur = conn.cursor()
            self.payday.mark_transfer(cur, amount)
            conn.commit()

        # Forces a load with current state in dict
        after_transfer = PaydayModel.query.first().attrs_dict()

        expected = before_transfer['ntransfers'] + 1
        actual = after_transfer['ntransfers']
        assert_equals(actual, expected)

        expected = before_transfer['transfer_volume'] + amount
        actual = after_transfer['transfer_volume']
        assert_equals(actual, expected)

    def test_record_credit_updates_balance(self):
        alice = self.make_participant("alice")
        self.payday.record_credit(amount=Decimal("-1.00"),
                                  fee=Decimal("0.41"),
                                  error="",
                                  username="******")
        assert_equals(alice.balance, Decimal("0.59"))

    def test_record_credit_doesnt_update_balance_if_error(self):
        alice = self.make_participant("alice")
        self.payday.record_credit(amount=Decimal("-1.00"),
                                  fee=Decimal("0.41"),
                                  error="SOME ERROR",
                                  username="******")
        assert_equals(alice.balance, Decimal("0.00"))
 def run_payday(self):
     Payday(self.db).run()
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)
 def setUp(self):
     super(TestBillingCharges, self).setUp()
     self.payday = Payday(self.postgres)
     self.alice = self.make_participant('alice')
 def setUpClass(cls):
     TestPaydayBase.setUpClass()
     cls.payday = Payday(mock.Mock())  # Mock out the DB connection
 def setUp(self):
     super(TestBillingPayday, self).setUp()
     self.payday = Payday(self.db)
class TestBillingCharges(TestPaydayBase):
    BALANCED_ACCOUNT_URI = u'/v1/marketplaces/M123/accounts/A123'
    BALANCED_TOKEN = u'/v1/marketplaces/M123/accounts/A123/cards/C123'
    STRIPE_CUSTOMER_ID = u'cus_deadbeef'

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

    def test_mark_missing_funding(self):
        self.payday.start()
        before = self.fetch_payday()
        missing_count = before['ncc_missing']

        self.payday.mark_missing_funding()

        after = self.fetch_payday()
        self.assertEqual(after['ncc_missing'], missing_count + 1)

    def test_mark_charge_failed(self):
        self.payday.start()
        before = self.fetch_payday()
        fail_count = before['ncc_failing']

        with self.db.get_cursor() as cursor:
            self.payday.mark_charge_failed(cursor)

        after = self.fetch_payday()
        self.assertEqual(after['ncc_failing'], fail_count + 1)

    def test_mark_charge_success(self):
        self.payday.start()
        charge_amount, fee = 4, 2

        with self.db.get_cursor() as cursor:
            self.payday.mark_charge_success(cursor, charge_amount, fee)

        # verify paydays
        actual = self.fetch_payday()
        self.assertEqual(actual['ncharges'], 1)

    @mock.patch('stripe.Charge')
    def test_charge_on_stripe(self, ba):
        amount_to_charge = Decimal('10.00')  # $10.00 USD
        expected_fee = Decimal('0.61')
        charge_amount, fee, msg = self.payday.charge_on_stripe(
            self.alice.username, self.STRIPE_CUSTOMER_ID, amount_to_charge)

        assert_equals(charge_amount, amount_to_charge + fee)
        assert_equals(fee, expected_fee)
        self.assertTrue(ba.find.called_with(self.STRIPE_CUSTOMER_ID))
        customer = ba.find.return_value
        self.assertTrue(
            customer.debit.called_with(int(charge_amount * 100),
                                       self.alice.username))

    @mock.patch('balanced.Account')
    def test_charge_on_balanced(self, ba):
        amount_to_charge = Decimal('10.00')  # $10.00 USD
        expected_fee = Decimal('0.61')
        charge_amount, fee, msg = self.payday.charge_on_balanced(
            self.alice.username, self.BALANCED_ACCOUNT_URI, amount_to_charge)
        self.assertEqual(charge_amount, amount_to_charge + fee)
        self.assertEqual(fee, expected_fee)
        self.assertTrue(ba.find.called_with(self.BALANCED_ACCOUNT_URI))
        customer = ba.find.return_value
        self.assertTrue(
            customer.debit.called_with(int(charge_amount * 100),
                                       self.alice.username))

    @mock.patch('balanced.Account')
    def test_charge_on_balanced_small_amount(self, ba):
        amount_to_charge = Decimal('0.06')  # $0.06 USD
        expected_fee = Decimal('0.59')
        expected_amount = Decimal('10.00')
        charge_amount, fee, msg = \
            self.payday.charge_on_balanced(self.alice.username,
                                           self.BALANCED_ACCOUNT_URI,
                                           amount_to_charge)
        assert_equals(charge_amount, expected_amount)
        assert_equals(fee, expected_fee)
        customer = ba.find.return_value
        self.assertTrue(
            customer.debit.called_with(int(charge_amount * 100),
                                       self.alice.username))

    @mock.patch('balanced.Account')
    def test_charge_on_balanced_failure(self, ba):
        amount_to_charge = Decimal('0.06')  # $0.06 USD
        error_message = 'Woah, crazy'
        ba.find.side_effect = balanced.exc.HTTPError(error_message)
        charge_amount, fee, msg = self.payday.charge_on_balanced(
            self.alice.username, self.BALANCED_ACCOUNT_URI, amount_to_charge)
        assert_equals(msg, error_message)
 def setUp(self):
     self.payday = Payday(self.db)
 def setUp(self):
     super(TestPaydayBase, self).setUp()
     self.payday = Payday(self.db)
     self.payday.start()
     self.tipper = self.make_participant('lgtest')
     self.balanced_account_uri = '/v1/marketplaces/M123/accounts/A123'
class TestBillingTransfer(TestPaydayBase):
    def setUp(self):
        super(TestPaydayBase, self).setUp()
        self.payday = Payday(self.db)
        self.payday.start()
        self.tipper = self.make_participant('lgtest')
        self.balanced_account_uri = '/v1/marketplaces/M123/accounts/A123'

    def test_transfer(self):
        amount = Decimal('1.00')
        sender = self.make_participant('test_transfer_sender',
                                       pending=0,
                                       balance=1)
        recipient = self.make_participant('test_transfer_recipient',
                                          pending=0,
                                          balance=1)

        result = self.payday.transfer(sender.username, recipient.username,
                                      amount)
        assert_equals(result, True)

        # no balance remaining for a second transfer
        result = self.payday.transfer(sender.username, recipient.username,
                                      amount)
        assert_equals(result, False)

    def test_debit_participant(self):
        amount = Decimal('1.00')
        subject = self.make_participant('test_debit_participant',
                                        pending=0,
                                        balance=1)

        initial_amount = subject.balance

        with self.db.get_cursor() as cursor:
            self.payday.debit_participant(cursor, subject.username, amount)

        subject = Participant.from_username('test_debit_participant')

        expected = initial_amount - amount
        actual = subject.balance
        assert_equals(actual, expected)

        # this will fail because not enough balance
        with self.db.get_cursor() as cursor:
            with self.assertRaises(IntegrityError):
                self.payday.debit_participant(cursor, subject.username, amount)

    def test_skim_credit(self):
        actual = skim_credit(Decimal('10.00'))
        assert actual == (Decimal('10.00'), Decimal('0.00')), actual

    def test_credit_participant(self):
        amount = Decimal('1.00')
        subject = self.make_participant('test_credit_participant',
                                        pending=0,
                                        balance=1)

        initial_amount = subject.pending

        with self.db.get_cursor() as cursor:
            self.payday.credit_participant(cursor, subject.username, amount)

        subject = Participant.from_username(
            'test_credit_participant')  # reload

        expected = initial_amount + amount
        actual = subject.pending
        assert_equals(actual, expected)

    def test_record_transfer(self):
        amount = Decimal('1.00')
        subjects = ['jim', 'kate', 'bob']

        for subject in subjects:
            self.make_participant(subject, balance=1, pending=0)

        with self.db.get_cursor() as cursor:
            # Tip 'jim' twice
            for recipient in ['jim'] + subjects:
                self.payday.record_transfer(cursor, self.tipper.username,
                                            recipient, amount)

        for subject in subjects:
            # 'jim' is tipped twice
            expected = amount * 2 if subject == 'jim' else amount
            actual = self.db.one(
                "SELECT sum(amount) FROM transfers "
                "WHERE tippee=%s", (subject, ))
            assert_equals(actual, expected)

    def test_record_transfer_invalid_participant(self):
        amount = Decimal('1.00')

        with self.db.get_cursor() as cursor:
            with assert_raises(IntegrityError):
                self.payday.record_transfer(cursor, 'idontexist', 'nori',
                                            amount)

    def test_mark_transfer(self):
        amount = Decimal('1.00')

        # Forces a load with current state in dict
        before_transfer = self.fetch_payday()

        with self.db.get_cursor() as cursor:
            self.payday.mark_transfer(cursor, amount)

        # Forces a load with current state in dict
        after_transfer = self.fetch_payday()

        expected = before_transfer['ntransfers'] + 1
        actual = after_transfer['ntransfers']
        assert_equals(actual, expected)

        expected = before_transfer['transfer_volume'] + amount
        actual = after_transfer['transfer_volume']
        assert_equals(actual, expected)

    def test_record_credit_updates_balance(self):
        self.payday.record_credit(amount=Decimal("-1.00"),
                                  fee=Decimal("0.41"),
                                  error="",
                                  username="******")
        alice = Participant.from_username('alice')
        assert_equals(alice.balance, Decimal("0.59"))

    def test_record_credit_doesnt_update_balance_if_error(self):
        self.payday.record_credit(amount=Decimal("-1.00"),
                                  fee=Decimal("0.41"),
                                  error="SOME ERROR",
                                  username="******")
        alice = Participant.from_username('alice')
        assert_equals(alice.balance, Decimal("0.00"))
Beispiel #59
0
 def setUp(self):
     super(GittipPaydayTest, self).setUp()
     self.payday = Payday(self.db)
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'))