def test_non_post_is_405(self): self.make_participant('alice', claimed_time=utcnow(), is_admin=True) self.make_participant('bob', claimed_time=utcnow()) actual = self.client.GxT( '/bob/history/record-an-exchange' , auth_as='alice' ).code assert actual == 405
def test_withdrawals_work(self): self.make_participant('alice', claimed_time=utcnow(), is_admin=True) self.make_participant('bob', claimed_time=utcnow(), balance=20) self.record_an_exchange('-7', '0', 'noted', make_participants=False) expected = Decimal('13.00') SQL = "SELECT balance FROM participants WHERE username='******'" actual = self.db.one(SQL) assert actual == expected
def test_withdrawals_work(self): self.make_participant("alice", claimed_time=utcnow(), is_admin=True) self.make_participant("bob", claimed_time=utcnow(), balance=20) self.record_an_exchange("-7", "0", "noted", False) expected = Decimal("13.00") SQL = "SELECT balance FROM participants WHERE username='******'" actual = self.db.one(SQL) assert actual == expected
def test_route_should_belong_to_user_else_400(self): alice = self.make_participant('alice', claimed_time=utcnow(), is_admin=True) self.make_participant('bob', claimed_time=utcnow()) route = ExchangeRoute.insert(alice, 'paypal', '*****@*****.**') response = self.record_an_exchange({'amount': '10', 'fee': '0', 'route_id': route.id}, False) assert response.code == 400 assert response.body == "Route doesn't exist"
def test_withdrawals_work(self): self.make_participant('alice', claimed_time=utcnow(), is_admin=True) self.make_participant('bob', claimed_time=utcnow(), balance=20) self.record_an_exchange('-7', '0', 'noted', False) expected = [{"balance": Decimal('13.00')}] SQL = "SELECT balance FROM participants WHERE id='bob'" actual = list(gittip.db.fetchall(SQL)) assert actual == expected, actual
def setup_tips(*recs): """Setup some participants and tips. recs is a list of: ("tipper", "tippee", '2.00', False) ^ |-- good cc? good_cc can be True, False, or None """ tips = [] _participants = {} for rec in recs: defaults = good_cc, payin_suspended, claimed = (True, False, True) if len(rec) == 3: tipper, tippee, amount = rec elif len(rec) == 4: tipper, tippee, amount, good_cc = rec payin_suspended, claimed = (False, True) elif len(rec) == 5: tipper, tippee, amount, good_cc, payin_suspended = rec claimed = True elif len(rec) == 6: tipper, tippee, amount, good_cc, payin_suspended, claimed = rec assert good_cc in (True, False, None), good_cc assert payin_suspended in (True, False), payin_suspended assert claimed in (True, False), claimed _participants[tipper] = (good_cc, payin_suspended, claimed) if tippee not in _participants: _participants[tippee] = defaults now = utcnow() tips.append({ "ctime": now , "mtime": now , "tipper": tipper , "tippee": tippee , "amount": Decimal(amount) }) then = utcnow() - datetime.timedelta(seconds=3600) participants = [] for participant_id, (good_cc, payin_suspended, claimed) in _participants.items(): rec = {"id": participant_id} if good_cc is not None: rec["last_bill_result"] = "" if good_cc else "Failure!" rec["balanced_account_uri"] = "/v1/blah/blah/" + participant_id rec["payin_suspended"] = payin_suspended if claimed: rec["claimed_time"] = then participants.append(rec) return ["participants"] + participants + ["tips"] + tips
def test_tip(self, log, transfer): self.db.run(""" UPDATE participants SET balance=1 , balanced_customer_href=%s , is_suspicious=False WHERE username='******' """, (self.BALANCED_CUSTOMER_HREF,)) amount = D('1.00') invalid_amount = D('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
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)
def test_post_non_team_member_adds_member_returns_403(self): client, csrf_token = self.make_client_and_csrf() self.make_team_and_participant() self.make_participant("bob", claimed_time=utcnow()) response = client.post('/team/members/alice.json' , { 'take': '0.01' , 'csrf_token': csrf_token } , user='******' ) actual = response.code assert actual == 200, actual response = client.post('/team/members/bob.json' , { 'take': '0.01' , 'csrf_token': csrf_token } , user='******' ) actual = response.code assert actual == 403, actual
def test_joining_and_leaving_community(self): self.make_participant("alice", claimed_time=utcnow()) response = self.client.GET('/for/communities.json', auth_as='alice') assert len(json.loads(response.body)['communities']) == 0 response = self.client.POST( '/for/communities.json' , {'name': 'Test', 'is_member': 'true'} , auth_as='alice' ) communities = json.loads(response.body)['communities'] assert len(communities) == 1 assert communities[0]['name'] == 'Test' assert communities[0]['nmembers'] == 1 response = self.client.POST( '/for/communities.json' , {'name': 'Test', 'is_member': 'false'} , auth_as='alice' ) response = self.client.GET('/for/communities.json', auth_as='alice') assert len(json.loads(response.body)['communities']) == 0 # Check that the empty community was deleted community = Community.from_slug('test') assert not community
def sign_in(self, cookies): """Start a new session for the user. """ token = uuid.uuid4().hex expires = utcnow() + SESSION_TIMEOUT self.participant.update_session(token, expires) set_cookie(cookies, SESSION, token, expires)
def test_api_returns_amount_and_totals(self): "Test that we get correct amounts and totals back on POSTs to subscription.json" # First, create some test data # We need accounts now = utcnow() self.make_team("A", is_approved=True) self.make_team("B", is_approved=True) self.make_participant("alice", claimed_time=now, last_bill_result='') # Then, add a $1.50 and $3.00 subscription response1 = self.client.POST( "/A/subscription.json" , {'amount': "1.50"} , auth_as='alice' ) response2 = self.client.POST( "/B/subscription.json" , {'amount': "3.00"} , auth_as='alice' ) # Confirm we get back the right amounts. first_data = json.loads(response1.body) second_data = json.loads(response2.body) assert first_data['amount'] == "1.50" assert first_data['total_giving'] == "1.50" assert second_data['amount'] == "3.00" assert second_data['total_giving'] == "4.50"
def test_post_can_leave_community(self): client, csrf_token = self.make_client_and_csrf() community = 'Test' self.make_participant("alice", claimed_time=utcnow()) response = client.post('/for/communities.json' , { 'name': community , 'is_member': 'true' , 'csrf_token': csrf_token } , user='******' ) response = client.post('/for/communities.json' , { 'name': community , 'is_member': 'false' , 'csrf_token': csrf_token } , user='******' ) response = client.get('/for/communities.json', 'alice') actual = len(json.loads(response.body)['communities']) assert actual == 0, actual
def setUp(self): super(Harness, self).setUp() now = utcnow() for idx, username in enumerate(["alice", "bob", "carl"], start=1): self.make_participant(username, claimed_time=now) twitter_account = TwitterAccount(idx, {"screen_name": username}) Participant(username).take_over(twitter_account)
def test_start_zero_out_and_get_participants(self, log): self.make_participant( "bob", balance=10, claimed_time=None, pending=1, balanced_customer_href=self.BALANCED_CUSTOMER_HREF ) self.make_participant( "carl", balance=10, claimed_time=utcnow(), pending=1, balanced_customer_href=self.BALANCED_CUSTOMER_HREF ) self.db.run( """ UPDATE participants SET balance=0 , claimed_time=null , pending=null , balanced_customer_href=%s WHERE username='******' """, (self.BALANCED_CUSTOMER_HREF,), ) 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()
def test_post_user_is_not_member_or_team_returns_403(self): client, csrf_token = self.make_client_and_csrf() self.make_team_and_participant() self.make_participant("bob", claimed_time=utcnow(), number='plural') response = client.post('/team/members/alice.json' , { 'take': '0.01' , 'csrf_token': csrf_token } , user='******' ) actual = response.code assert actual == 200 response = client.post('/team/members/bob.json' , { 'take': '0.01' , 'csrf_token': csrf_token } , user='******' ) actual = response.code assert actual == 200 response = client.post('/team/members/alice.json' , { 'csrf_token': csrf_token } , user='******' ) actual = response.code assert actual == 403
def also_prune_variant(self, also_prune, tippees=1): now = utcnow() self.make_participant("test_tippee1", claimed_time=now) self.make_participant("test_tippee2", claimed_time=now) self.make_participant("test_tipper", claimed_time=now) data = [ {'username': '******', 'platform': 'gittip', 'amount': '1.00'}, {'username': '******', 'platform': 'gittip', 'amount': '2.00'} ] response = self.client.POST( '/test_tipper/tips.json' , body=json.dumps(data) , content_type='application/json' , auth_as='test_tipper' ) assert response.code == 200 assert len(json.loads(response.body)) == 2 response = self.client.POST( '/test_tipper/tips.json?also_prune=' + also_prune , body=json.dumps([{ 'username': '******' , 'platform': 'gittip' , 'amount': '1.00' }]) , content_type='application/json' , auth_as='test_tipper' ) assert response.code == 200 response = self.client.GET('/test_tipper/tips.json', auth_as='test_tipper') assert response.code == 200 assert len(json.loads(response.body)) == tippees
def test_get_amount_and_total_back_from_api(self): "Test that we get correct amounts and totals back on POSTs to tip.json" # First, create some test data # We need accounts now = utcnow() self.make_participant("test_tippee1", claimed_time=now) self.make_participant("test_tippee2", claimed_time=now) self.make_participant("test_tipper") # Then, add a $1.50 and $3.00 tip response1 = self.client.POST( "/test_tippee1/tip.json" , {'amount': "1.00"} , auth_as='test_tipper' ) response2 = self.client.POST( "/test_tippee2/tip.json" , {'amount': "3.00"} , auth_as='test_tipper' ) # Confirm we get back the right amounts. first_data = json.loads(response1.body) second_data = json.loads(response2.body) assert first_data['amount'] == "1.00" assert first_data['total_giving'] == "1.00" assert second_data['amount'] == "3.00" assert second_data['total_giving'] == "4.00"
def setUp(self): super(Harness, self).setUp() now = utcnow() for idx, username in enumerate(['alice', 'bob', 'carl'], start=1): self.make_participant(username, claimed_time=now) twitter_account = TwitterAccount(idx, {'screen_name': username}) Participant.from_username(username).take_over(twitter_account)
def make_participant(self, username, **kw): # At this point wireup.db() has been called, but not ... wireup.username_restrictions(self.client.website) participant = Participant.with_random_username() participant.change_username(username) if 'elsewhere' in kw or 'claimed_time' in kw: username = participant.username platform = kw.pop('elsewhere', 'github') self.seq += 1 self.db.run(""" INSERT INTO elsewhere (platform, user_id, user_name, participant) VALUES (%s,%s,%s,%s) """, (platform, self.seq, username, username)) # Update participant if kw: if kw.get('claimed_time') == 'now': kw['claimed_time'] = utcnow() cols, vals = zip(*kw.items()) cols = ', '.join(cols) placeholders = ', '.join(['%s']*len(vals)) participant = self.db.one(""" UPDATE participants SET ({0}) = ({1}) WHERE username=%s RETURNING participants.*::participants """.format(cols, placeholders), vals+(participant.username,)) return participant
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.id, "claimed_time": utcnow()} ts_start = utcnow() result = self.payday.tip(participant, tip, ts_start) assert_equals(result, 1) result = transfer.called_with(participant["id"], 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)
def test_get_tip_distribution_handles_a_non_standard_amount(self): tip = Tip(tipper='alice', tippee='bob', amount='5.37', ctime=utcnow()) self.session.add(tip) self.session.commit() expected = ([[-1, 1, Decimal('5.37'), 1.0, Decimal('1')]], 1.0, Decimal('5.37')) actual = Participant(u'bob').get_tip_distribution() assert_equals(actual, expected)
def test_set_tip_to_patron(self): now = utcnow() self.make_participant("alice", claimed_time=now, goal="-1") self.make_participant("bob", claimed_time=now) response = self.client.PxST("/alice/tip.json", {"amount": "10.00"}, auth_as="bob") assert "user doesn't accept tips" in response.body assert response.code == 400
def test_post_non_team_member_adds_member_returns_403(self): self.make_participant("bob", claimed_time=utcnow()) response = self.client.POST('/team/members/alice.json', {'take': '0.01'}, auth_as='team') assert response.code == 200 response = self.client.PxST('/team/members/bob.json', {'take': '0.01'}, auth_as='alice') assert response.code == 403
def test_user_from_expired_session_is_anonymous(self): self.make_participant('alice') user = User.from_username('alice') user.sign_in(SimpleCookie()) token = user.participant.session_token user.participant.set_session_expires(utcnow()) user = User.from_session_token(token) assert user.ANON
def test_get_response(self): now = utcnow() self.make_participant("test_tipper", claimed_time=now) response = self.client.GET('/test_tipper/tips.json', auth_as='test_tipper') assert response.code == 200 assert len(json.loads(response.body)) == 0 # empty array
def test_log_in_with_old_session(self): alice = self.make_participant('alice') alice.update_session('x', utcnow() - timedelta(days=1)) alice.authenticated = True cookies = SimpleCookie() alice.sign_in(cookies) print(cookies) self.check_with_about_me('alice', cookies)
def test_get_existing_user(self): self.make_participant("alice", claimed_time=utcnow()) response = self.client.GET('/lookup.json?query={}'.format('alice')) data = json.loads(response.body) assert len(data) == 1 assert data[0]['id'] != -1
def test_to_age_barely_works(): now = utcnow() actual = to_age(now, dt_now=now) assert actual == "in just a moment" wait = timedelta(seconds=0.5) actual = to_age(now - wait, dt_now=now) assert actual == "just a moment ago"
def keep_signed_in(self, cookies): """Extend the user's current session. """ new_expires = utcnow() + SESSION_TIMEOUT if new_expires - self.participant.session_expires > SESSION_REFRESH: self.participant.set_session_expires(new_expires) token = self.participant.session_token set_cookie(cookies, SESSION, token, expires=new_expires)
def to_age(dt, loc, **kw): return format_timedelta(dt - utcnow(), locale=loc, **kw)
def make_alice(self): return self.make_participant('alice', claimed_time=utcnow())
def setUp(self): Harness.setUp(self) now = utcnow() for username in ['alice', 'bob', 'carl']: p = self.make_participant(username, claimed_time=now, elsewhere='twitter') setattr(self, username, p)
def finish_email_verification(self, email, nonce): """Given an email address and a nonce as strings, return a three-tuple: - a ``VERIFICATION_*`` constant; - a list of packages if ``VERIFICATION_SUCCEEDED`` (``None`` otherwise), and - a boolean indicating whether the participant's PayPal address was updated if applicable (``None`` if not). """ _fail = VERIFICATION_FAILED, None, None if '' in (email.strip(), nonce.strip()): return _fail with self.db.get_cursor() as cursor: # Load an email record. Check for an address match, but don't check # the nonce at this point. We want to compare in constant time to # avoid timing attacks, and we'll do that below. record = self.get_email(email, cursor, and_lock=True) if record is None: # We don't have that email address on file. Maybe it used to be # on file but was explicitly removed (they followed an old link # after removing in the UI?), or maybe it was never on file in # the first place (they munged the querystring?). return _fail if record.nonce is None: # Nonces are nulled out only when updating to mark an email # address as verified; we always set a nonce when inserting. # Therefore, the main way to get a null nonce is to issue a # link, follow it, and follow it again. # All records with a null nonce should be verified, though not # all verified records will have a null nonce. That is, it's # possible to land here with an already-verified address, and # this is in fact expected when verifying package ownership via # an already-verified address. assert record.verified return VERIFICATION_REDUNDANT, None, None # *Now* verify that the nonce given matches the one expected, along # with the time window for verification. if not constant_time_compare(record.nonce, nonce): return _fail if (utcnow() - record.verification_start) > EMAIL_HASH_TIMEOUT: return _fail # And now we can load any packages associated with the nonce, and # save the address. packages = self.get_packages_claiming(cursor, nonce) paypal_updated = None try: if packages: paypal_updated = False self.finish_package_claims(cursor, nonce, *packages) self.save_email_address(cursor, email) has_no_paypal = not self.get_payout_routes(good_only=True) if packages and has_no_paypal: self.set_paypal_address(email, cursor) paypal_updated = True except IntegrityError: return VERIFICATION_STYMIED, None, None return VERIFICATION_SUCCEEDED, packages, paypal_updated
def test_gais_gets_age_in_seconds(self): now = utcnow() alice = self.make_participant('alice', claimed_time=now) actual = alice.get_age_in_seconds() assert 0 < actual < 1
def setUp(self): Harness.setUp(self) self.make_participant("team", claimed_time=utcnow(), number='plural') self.make_participant("alice", claimed_time=utcnow())
def get_age_in_seconds(self): out = -1 if self.claimed_time is not None: now = utcnow() out = (now - self.claimed_time).total_seconds() return out
def setup_tips(*recs): """Setup some participants and tips. recs is a list of: ("tipper", "tippee", '2.00', True, False, True, "github", "12345") ^ ^ ^ | | | | | -- claimed? | -- is_suspicious? |-- good cc? tipper must be a unicode tippee can be None or unicode amount can be None or unicode good_cc can be True, False, or None is_suspicious can be True, False, or None claimed can be True or False platform can be unicode user_id can be unicode """ tips = [] _participants = {} randid = lambda: unicode(random.randint(1, 1000000)) for rec in recs: good_cc, is_suspicious, claimed, platform, user_id = \ (True, False, True, "github", randid()) if len(rec) == 3: tipper, tippee, amount = rec elif len(rec) == 4: tipper, tippee, amount, good_cc = rec is_suspicious, claimed = (False, True) elif len(rec) == 5: tipper, tippee, amount, good_cc, is_suspicious = rec claimed = True elif len(rec) == 6: tipper, tippee, amount, good_cc, is_suspicious, claimed = rec elif len(rec) == 7: tipper, tippee, amount, good_cc, is_suspicious, claimed, platform \ = rec elif len(rec) == 8: tipper, tippee, amount, good_cc, is_suspicious, claimed, \ platform, user_id = rec else: raise Exception(rec) assert good_cc in (True, False, None), good_cc assert is_suspicious in (True, False, None), is_suspicious _participants[tipper] = \ (good_cc, is_suspicious, True, platform, user_id) if tippee is None: continue assert claimed in (True, False), claimed # refers to tippee if tippee not in _participants: _participants[tippee] = (None, False, claimed, "github", randid()) now = utcnow() tips.append({ "ctime": now, "mtime": now, "tipper": tipper, "tippee": tippee, "amount": Decimal(amount) }) then = utcnow() - datetime.timedelta(seconds=3600) participants = [] elsewhere = [] for username, crap in _participants.items(): (good_cc, is_suspicious, claimed, platform, user_id) = crap username_key = "login" if platform == 'github' else "screen_name" elsewhere.append({ "platform": platform, "user_id": user_id, "participant": username, "user_info": { "id": user_id, username_key: username } }) rec = {"username": username} if good_cc is not None: rec["last_bill_result"] = "" if good_cc else "Failure!" rec["balanced_account_uri"] = "/v1/blah/blah/" + username rec["is_suspicious"] = is_suspicious if claimed: rec["claimed_time"] = then participants.append(rec) return ["participants"] + participants \ + ["tips"] + tips \ + ["elsewhere"] + elsewhere
def make_participant(self, *a, **kw): kw['claimed_time'] = utcnow() return Harness.make_participant(self, *a, **kw)
def add_email(self, email): """Add an email address for a participant. This is called when adding a new email address, and when resending the verification email for an unverified email address. :param unicode email: the email address to add :returns: ``None`` :raises EmailAlreadyVerified: if the email is already verified for this participant :raises EmailTaken: if the email is verified for a different participant :raises TooManyEmailAddresses: if the participant already has 10 emails :raises Throttled: if the participant adds too many emails too quickly """ # Check that this address isn't already verified owner = self.db.one( """ SELECT p.username FROM emails e INNER JOIN participants p ON e.participant_id = p.id WHERE e.address = %(email)s AND e.verified IS true """, locals()) if owner: if owner == self.username: raise EmailAlreadyVerified(email) else: raise EmailTaken(email) if len(self.get_emails()) > 9: raise TooManyEmailAddresses(email) nonce = str(uuid.uuid4()) verification_start = utcnow() try: with self.db.get_cursor() as c: self.app.add_event( c, 'participant', dict(id=self.id, action='add', values=dict(email=email))) c.run( """ INSERT INTO emails (address, nonce, verification_start, participant_id) VALUES (%s, %s, %s, %s) """, (email, nonce, verification_start, self.id)) except IntegrityError: nonce = self.db.one( """ UPDATE emails SET verification_start=%s WHERE participant_id=%s AND address=%s AND verified IS NULL RETURNING nonce """, (verification_start, self.id, email)) if not nonce: return self.add_email(email) base_url = gratipay.base_url username = self.username_lower encoded_email = encode_for_querystring(email) link = "{base_url}/~{username}/emails/verify.html?email2={encoded_email}&nonce={nonce}" self.app.email_queue.put(self, 'verification', email=email, link=link.format(**locals()), include_unsubscribe=False) if self.email_address: self.app.email_queue.put( self, 'verification_notice', new_email=email, include_unsubscribe=False # Don't count this one against their sending quota. # It's going to their own verified address, anyway. , _user_initiated=False)
def make_participant(self, username, **kw): """Factory for :py:class:`~gratipay.models.participant.Participant`. """ participant = self.db.one( """ INSERT INTO participants (username, username_lower) VALUES (%s, %s) RETURNING participants.*::participants """, (username, username.lower())) if 'id' in kw: new_id = kw.pop('id') self.db.run('UPDATE participants SET id=%s WHERE id=%s', (new_id, participant.id)) participant.set_attributes(id=new_id) if 'elsewhere' in kw or 'claimed_time' in kw: platform = kw.pop('elsewhere', 'github') self.db.run( """ INSERT INTO elsewhere (platform, user_id, user_name, participant) VALUES (%s,%s,%s,%s) """, (platform, participant.id, username, username)) # Insert exchange routes if 'last_bill_result' in kw: route = ExchangeRoute.insert(participant, 'braintree-cc', '/cards/foo') route.update_error(kw.pop('last_bill_result')) if 'last_paypal_result' in kw: route = ExchangeRoute.insert(participant, 'paypal', '*****@*****.**') route.update_error(kw.pop('last_paypal_result')) # Handle email address if 'email_address' in kw: address = kw.pop('email_address') if address: self.add_and_verify_email(participant, address) self.db.run( 'DELETE FROM email_messages') # don't confuse email tests # Update participant verified_in = kw.pop('verified_in', []) if kw: if kw.get('claimed_time') == 'now': kw['claimed_time'] = utcnow() cols, vals = zip(*kw.items()) cols = ', '.join(cols) placeholders = ', '.join(['%s'] * len(vals)) participant = self.db.one( """ UPDATE participants SET ({0}) = ({1}) WHERE username=%s RETURNING participants.*::participants """.format(cols, placeholders), vals + (username, )) # Verify identity countries = [verified_in ] if type(verified_in) in (str, unicode) else verified_in for code in countries: country = self.db.one("SELECT id FROM countries WHERE code=%s", (code, )) participant.store_identity_info(country, 'nothing-enforced', {'name': username}) participant.set_identity_verification(country, True) return participant
def to_age(dt): if isinstance(dt, datetime): return dt - utcnow() return datedelta(dt - date.today())
def test_get_can_get_communities_for_user(self): self.make_participant("alice", claimed_time=utcnow()) response = self.client.GET('/for/communities.json', auth_as='alice') assert len(json.loads(response.body)['communities']) == 0
def setup_tips(*recs): """Setup some participants and tips. recs is a list of: ("tipper", "tippee", '2.00', True, False, True) ^ ^ ^ | | | | | -- claimed? | -- is_suspicious? |-- good cc? good_cc can be True, False, or None is_suspicious can be True, False, or None claimed can be True or False """ tips = [] _participants = {} for rec in recs: defaults = good_cc, is_suspicious, claimed = (True, False, True) if len(rec) == 3: tipper, tippee, amount = rec elif len(rec) == 4: tipper, tippee, amount, good_cc = rec is_suspicious, claimed = (False, True) elif len(rec) == 5: tipper, tippee, amount, good_cc, is_suspicious = rec claimed = True elif len(rec) == 6: tipper, tippee, amount, good_cc, is_suspicious, claimed = rec assert good_cc in (True, False, None), good_cc assert is_suspicious in (True, False, None), is_suspicious assert claimed in (True, False), claimed _participants[tipper] = (good_cc, is_suspicious, claimed) if tippee not in _participants: _participants[tippee] = defaults now = utcnow() tips.append({ "ctime": now, "mtime": now, "tipper": tipper, "tippee": tippee, "amount": Decimal(amount) }) then = utcnow() - datetime.timedelta(seconds=3600) participants = [] for participant_id, (good_cc, is_suspicious, claimed) in _participants.items(): rec = {"id": participant_id} if good_cc is not None: rec["last_bill_result"] = "" if good_cc else "Failure!" rec["balanced_account_uri"] = "/v1/blah/blah/" + participant_id rec["is_suspicious"] = is_suspicious if claimed: rec["claimed_time"] = then participants.append(rec) return ["participants"] + participants + ["tips"] + tips
def test_non_admin_is_404(self): self.make_participant('alice', claimed_time=utcnow()) self.make_participant('bob', claimed_time=utcnow()) actual = self.record_an_exchange('10', '0', 'foo', False).code assert actual == 404, actual
def make_participants(self): now = utcnow() self.make_participant('alice', claimed_time=now, is_admin=True) self.make_participant('bob', claimed_time=now)
def test_non_post_is_405(self): self.make_participant('alice', claimed_time=utcnow(), is_admin=True) self.make_participant('bob', claimed_time=utcnow()) actual = \ self.client.get('/bob/history/record-an-exchange', 'alice').code assert actual == 405, actual
def check_connect_token(self, token): return (self.connect_token and constant_time_compare(self.connect_token, token) and self.connect_expires > utcnow())
def test_to_age_formatting_works(): actual = to_age(utcnow(), fmt_past="Cheese, for {age}!") assert actual == "Cheese, for just a moment!", actual
def make_connect_token(self): token = uuid.uuid4().hex expires = utcnow() + CONNECT_TOKEN_TIMEOUT return self.save_connect_token(token, expires)
def test_to_age_barely_works(): actual = to_age(utcnow()) assert actual == "just a moment ago", actual
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())
def test_claiming_participant(self): now = utcnow() self.participant.set_as_claimed() actual = self.participant.claimed_time - now expected = datetime.timedelta(seconds=0.1) assert actual < expected
def to_age(dt, loc): return format_timedelta(dt - utcnow(), add_direction=True, locale=loc)
def test_withdrawals_take_fee_out_of_balance(self): self.make_participant('alice', claimed_time=utcnow(), is_admin=True) self.make_participant('bob', claimed_time=utcnow(), balance=20) self.record_an_exchange('-7', '1.13', 'noted', False) SQL = "SELECT balance FROM participants WHERE username='******'" assert self.db.one(SQL) == Decimal('11.87')
def make_team_and_participant(self): self.make_participant("team", claimed_time=utcnow(), number='plural') self.make_participant("alice", claimed_time=utcnow())