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)
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)
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, LOOP_PACHINKO) self.payday.pachinko(ts_start, participants) assert Participant.from_username('alice').pending == D('0.01') assert Participant.from_username('bob').pending == D('0.01') def test_pachinko_sees_current_take(self): a_team = self.make_participant('a_team', claimed_time='now', number='plural', balance=20, \ pending=0) alice = self.make_participant('alice', claimed_time='now', balance=0, pending=0) a_team.add_member(alice) a_team.set_take_for(alice, D('1.00'), alice) ts_start = self.payday.start() participants = self.payday.genparticipants(ts_start, LOOP_PACHINKO) self.payday.pachinko(ts_start, participants) assert Participant.from_username('alice').pending == D('1.00') def test_pachinko_ignores_take_set_after_payday_starts(self): a_team = self.make_participant('a_team', claimed_time='now', number='plural', balance=20, \ pending=0) alice = self.make_participant('alice', claimed_time='now', balance=0, pending=0) a_team.add_member(alice) a_team.set_take_for(alice, D('0.33'), alice) ts_start = self.payday.start() a_team.set_take_for(alice, D('1.00'), alice) participants = self.payday.genparticipants(ts_start, LOOP_PACHINKO) self.payday.pachinko(ts_start, participants) assert Participant.from_username('alice').pending == D('0.33') def test_pachinko_ignores_take_thats_already_been_processed(self): a_team = self.make_participant('a_team', claimed_time='now', number='plural', balance=20, \ pending=0) alice = self.make_participant('alice', claimed_time='now', balance=0, pending=0) a_team.add_member(alice) a_team.set_take_for(alice, D('0.33'), alice) ts_start = self.payday.start() a_team.set_take_for(alice, D('1.00'), alice) for i in range(4): participants = self.payday.genparticipants(ts_start, LOOP_PACHINKO) self.payday.pachinko(ts_start, participants) assert Participant.from_username('alice').pending == D('0.33')
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)
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, LOOP_PACHINKO) self.payday.pachinko(ts_start, participants) assert Participant.from_username("alice").pending == D("0.01") assert Participant.from_username("bob").pending == D("0.01") def test_pachinko_sees_current_take(self): a_team = self.make_participant("a_team", claimed_time="now", number="plural", balance=20, pending=0) alice = self.make_participant("alice", claimed_time="now", balance=0, pending=0) a_team.add_member(alice) a_team.set_take_for(alice, D("1.00"), alice) ts_start = self.payday.start() participants = self.payday.genparticipants(ts_start, LOOP_PACHINKO) self.payday.pachinko(ts_start, participants) assert Participant.from_username("alice").pending == D("1.00") def test_pachinko_ignores_take_set_after_payday_starts(self): a_team = self.make_participant("a_team", claimed_time="now", number="plural", balance=20, pending=0) alice = self.make_participant("alice", claimed_time="now", balance=0, pending=0) a_team.add_member(alice) a_team.set_take_for(alice, D("0.33"), alice) ts_start = self.payday.start() a_team.set_take_for(alice, D("1.00"), alice) participants = self.payday.genparticipants(ts_start, LOOP_PACHINKO) self.payday.pachinko(ts_start, participants) assert Participant.from_username("alice").pending == D("0.33") def test_pachinko_ignores_take_thats_already_been_processed(self): a_team = self.make_participant("a_team", claimed_time="now", number="plural", balance=20, pending=0) alice = self.make_participant("alice", claimed_time="now", balance=0, pending=0) a_team.add_member(alice) a_team.set_take_for(alice, D("0.33"), alice) ts_start = self.payday.start() a_team.set_take_for(alice, D("1.00"), alice) for i in range(4): participants = self.payday.genparticipants(ts_start, LOOP_PACHINKO) self.payday.pachinko(ts_start, participants) assert Participant.from_username("alice").pending == D("0.33")
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
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)
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, LOOP_PACHINKO) self.payday.pachinko(ts_start, participants) assert Participant.from_username('alice').pending == D('0.01') assert Participant.from_username('bob').pending == D('0.01') def test_pachinko_sees_current_take(self): a_team = self.make_participant('a_team', claimed_time='now', number='plural', balance=20, \ pending=0) alice = self.make_participant('alice', claimed_time='now', balance=0, pending=0) a_team.add_member(alice) a_team.set_take_for(alice, D('1.00'), alice) ts_start = self.payday.start() participants = self.payday.genparticipants(ts_start, LOOP_PACHINKO) self.payday.pachinko(ts_start, participants) assert Participant.from_username('alice').pending == D('1.00') def test_pachinko_ignores_take_set_after_payday_starts(self): a_team = self.make_participant('a_team', claimed_time='now', number='plural', balance=20, \ pending=0) alice = self.make_participant('alice', claimed_time='now', balance=0, pending=0) a_team.add_member(alice) a_team.set_take_for(alice, D('0.33'), alice) ts_start = self.payday.start() a_team.set_take_for(alice, D('1.00'), alice) participants = self.payday.genparticipants(ts_start, LOOP_PACHINKO) self.payday.pachinko(ts_start, participants) assert Participant.from_username('alice').pending == D('0.33') def test_pachinko_ignores_take_thats_already_been_processed(self): a_team = self.make_participant('a_team', claimed_time='now', number='plural', balance=20, \ pending=0) alice = self.make_participant('alice', claimed_time='now', balance=0, pending=0) a_team.add_member(alice) a_team.set_take_for(alice, D('0.33'), alice) ts_start = self.payday.start() a_team.set_take_for(alice, D('1.00'), alice) for i in range(4): participants = self.payday.genparticipants(ts_start, LOOP_PACHINKO) self.payday.pachinko(ts_start, participants) assert Participant.from_username('alice').pending == D('0.33')