def test_v1_can_handle_rtd(client): file_upload = FileUpload(filename='requirements.txt', data=RTD) deps = json.loads(client.POST('/v1', data={'file': file_upload}).body) assert len(deps[0]['deps']) == 90 assert deps[0]['deps'][14] == dict(name='Django', version='1.8.3', license='BSD')
def test_v1_can_be_hit(client): file_upload = FileUpload(filename='requirements.txt', data='Flask==0.11.1') deps = json.loads(client.POST('/v1', data={'file': file_upload}).body) assert len(deps) == 1 assert len(deps[0]['deps']) == 6 assert deps[0]['deps'][1] == dict(name='Flask', version='0.11.1', license='BSD')
def test_v1_accepts_multiple_files(client): one = FileUpload(filename='requirements.txt', data='Flask==0.11.1') two = FileUpload(filename='requirements.txt', data='itsdangerous==0.24') class Kludge: def items(self): for f in (one, two): yield 'file', f deps = json.loads(client.POST('/v1', data=Kludge()).body) assert len(deps) == 2 assert len(deps[0]['deps']) == 6 assert deps[0]['deps'][1] == dict(name='Flask', version='0.11.1', license='BSD') assert len(deps[1]['deps']) == 1 assert deps[1]['deps'][0] == dict(name='itsdangerous', version='0.24', license='UNKNOWN')
def test_edit_with_invalid_image_type_raises_error(self): team = self.make_team(slug='enterprise', is_approved=True) invalid_image_types = ['tiff', 'gif', 'bmp', 'svg'] for i_type in invalid_image_types: data = {'image': FileUpload(IMAGE, 'logo.' + i_type)} response = self.client.PxST('/enterprise/edit/edit.json', data=data, auth_as='picard') assert response.code == 400 assert "Please upload a PNG or JPG image." in response.body assert team.load_image('original') == None
def test_edit_accepts_jpeg_and_png(self): team = self.make_team(slug='enterprise', is_approved=True) image_types = ['png', 'jpg', 'jpeg'] for i_type in image_types: team.save_image(original='', large='', small='', image_type='image/png') data = {'image': FileUpload(IMAGE, 'logo.'+i_type)} response = self.client.POST( '/enterprise/edit/edit.json' , data=data , auth_as='picard' ) assert response.code == 200 assert team.load_image('original') == IMAGE
def test_edit_supports_partial_updates(self): self.make_team(slug='enterprise', is_approved=True) edit_data = { 'product_or_service': 'We save galaxies.', 'homepage': 'http://starwars-enterprise.com/', 'image': FileUpload(IMAGE, 'logo.png'), } self.client.POST( '/enterprise/edit/edit.json' , data=edit_data , auth_as='picard' ) team = T('enterprise') assert team.name == 'The Enterprise' assert team.product_or_service == 'We save galaxies.' assert team.homepage == 'http://starwars-enterprise.com/' assert team.load_image('original') == IMAGE
def test_edit(self): self.make_team(slug='enterprise', is_approved=True) edit_data = { 'name': 'Enterprise', 'product_or_service': 'We save galaxies.', 'homepage': 'http://starwars-enterprise.com/', 'image': FileUpload(IMAGE, 'logo.png'), } data = json.loads(self.client.POST( '/enterprise/edit/edit.json' , data=edit_data , auth_as='picard' ).body) team = T('enterprise') assert data == team.to_dict() assert team.name == 'Enterprise' assert team.product_or_service == 'We save galaxies.' assert team.homepage == 'http://starwars-enterprise.com/' assert team.load_image('original') == IMAGE
def test_v1_can_hit_back(client): file_upload = FileUpload(filename='requirements.txt', data='Flasky garbage') deps = json.loads(client.POST('/v1', data={'file': file_upload}).body) assert len(deps) == 1 assert deps[0]['error'].startswith('Traceback')
def bodified(raw): """Convert a plain dict into a cgi.FieldStorage per Aspen. """ encoded = encode_multipart(BOUNDARY, raw) headers = BaseHeaders({b'Content-Type': b'multipart/form-data; boundary=' + BOUNDARY}) return aspen.body_parsers.formdata(encoded, headers) GOOD = { 'amount': '1000' , 'payment_method_nonce': 'fake-valid-nonce' , 'name': 'Alice Liddell' , 'email_address': '*****@*****.**' , 'on_mailing_list': 'yes' , 'promotion_logo': FileUpload(ORIGINAL, 'logo.png') , 'promotion_name': 'Wonderland' , 'promotion_url': 'http://www.example.com/' , 'promotion_twitter': 'thebestbutter' , 'promotion_message': 'Love me! Love me! Say that you love me!' } BAD = { 'amount': '1,000' , 'payment_method_nonce': 'deadbeef' * 5 , 'name': 'Alice Liddell' * 20 , 'email_address': 'alice' * 100 + '@example.com' , 'on_mailing_list': 'cheese' , 'promotion_logo': FileUpload(ORIGINAL, 'logo.gif') , 'promotion_name': 'Wonderland' * 100 , 'promotion_url': 'http://www.example.com/' + 'cheese' * 100 , 'promotion_twitter': 'thebestbutter' * 10 , 'promotion_message': 'Love me!' * 50
class TestTeams(Harness): valid_data = { 'name': 'Gratiteam', 'product_or_service': 'We make widgets.', 'homepage': 'http://gratipay.com/', 'onboarding_url': 'http://inside.gratipay.com/', 'todo_url': 'https://github.com/gratipay', 'agree_public': 'true', 'agree_payroll': 'true', 'agree_terms': 'true', 'image': FileUpload(IMAGE, 'logo.png'), } def post_new(self, data, auth_as='alice', expected=200): r = self.client.POST('/teams/create.json', data=data, auth_as=auth_as, raise_immediately=False) assert r.code == expected return r def test_harness_can_make_a_team(self): team = self.make_team() assert team.name == 'The Enterprise' assert team.owner == 'picard' def test_can_construct_from_slug(self): self.make_team() team = Team.from_slug('TheEnterprise') assert team.name == 'The Enterprise' assert team.owner == 'picard' def test_can_construct_from_id(self): team = Team.from_id(self.make_team().id) assert team.name == 'The Enterprise' assert team.owner == 'picard' @mock.patch('gratipay.models.team.Team.create_github_review_issue') def test_can_create_new_team(self, cgri): cgri.return_value = REVIEW_URL self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='') r = self.post_new(dict(self.valid_data)) team = self.db.one("SELECT * FROM teams") assert team assert team.owner == 'alice' assert json.loads(r.body)['review_url'] == team.review_url def test_all_fields_persist(self): self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='') self.post_new(dict(self.valid_data)) team = Team.from_slug('gratiteam') assert team.name == 'Gratiteam' assert team.homepage == 'http://gratipay.com/' assert team.product_or_service == 'We make widgets.' fallback = 'https://github.com/gratipay/team-review/issues#error-401' assert team.review_url in (REVIEW_URL, fallback) def test_casing_of_urls_survives(self): self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='') self.post_new( dict(self.valid_data, homepage='Http://gratipay.com/', onboarding_url='http://INSIDE.GRATipay.com/', todo_url='hTTPS://github.com/GRATIPAY')) team = Team.from_slug('gratiteam') assert team.homepage == 'Http://gratipay.com/' assert team.onboarding_url == 'http://INSIDE.GRATipay.com/' assert team.todo_url == 'hTTPS://github.com/GRATIPAY' def test_casing_of_slug_survives(self): self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='') data = dict(self.valid_data) data['name'] = 'GratiTeam' self.post_new(dict(data)) team = Team.from_slug('GratiTeam') assert team is not None assert team.slug_lower == 'gratiteam' def test_401_for_anon_creating_new_team(self): self.post_new(self.valid_data, auth_as=None, expected=401) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 def test_error_message_for_no_valid_email(self): self.make_participant('alice', claimed_time='now') r = self.post_new(dict(self.valid_data), expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "You must have a verified email address to apply for a new team." in r.body def test_error_message_for_no_payout_route(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**') r = self.post_new(dict(self.valid_data), expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "You must attach a PayPal account to apply for a new team." in r.body def test_error_message_for_public_review(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') data = dict(self.valid_data) del data['agree_public'] r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Sorry, you must agree to have your application publicly reviewed." in r.body def test_error_message_for_payroll(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') data = dict(self.valid_data) del data['agree_payroll'] r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Sorry, you must agree to be responsible for payroll." in r.body def test_error_message_for_terms(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') data = dict(self.valid_data) del data['agree_terms'] r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Sorry, you must agree to the terms of service." in r.body def test_error_message_for_missing_fields(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') data = dict(self.valid_data) del data['name'] r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Please fill out the 'Team Name' field." in r.body def test_error_message_for_bad_url(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') r = self.post_new(dict(self.valid_data, homepage='foo'), expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Please enter an http[s]:// URL for the 'Homepage' field." in r.body r = self.post_new(dict(self.valid_data, onboarding_url='foo'), expected=400) assert "an http[s]:// URL for the 'Self-onboarding Documentation URL' field." in r.body r = self.post_new(dict(self.valid_data, todo_url='foo'), expected=400) assert "Please enter an http[s]:// URL for the 'To-do URL' field." in r.body def test_error_message_for_invalid_team_name(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') data = dict(self.valid_data) data['name'] = '~Invalid:Name;' r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Sorry, your team name is invalid." in r.body def test_error_message_for_slug_collision(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') self.post_new(dict(self.valid_data)) r = self.post_new(dict(self.valid_data), expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 1 assert "Sorry, there is already a team using 'Gratiteam'." in r.body def test_approved_team_shows_up_on_homepage(self): self.make_team(is_approved=True) assert 'The Enterprise' in self.client.GET("/").body def test_unreviewed_team_shows_up_on_homepage(self): self.make_team(is_approved=None) assert 'The Enterprise' in self.client.GET("/").body def test_rejected_team_shows_up_on_homepage(self): self.make_team(is_approved=False) assert 'The Enterprise' in self.client.GET("/").body def test_stripping_required_inputs(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') data = dict(self.valid_data) data['name'] = " " r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Please fill out the 'Team Name' field." in r.body # Migrate Tips # ============ def test_migrate_tips_to_payment_instructions(self): alice = self.make_participant('alice', claimed_time='now') bob = self.make_participant('bob', claimed_time='now') self.make_participant('old_team') self.make_tip(alice, 'old_team', '1.00') self.make_tip(bob, 'old_team', '2.00') new_team = self.make_team('new_team', owner='old_team') ntips = new_team.migrate_tips() assert ntips == 2 payment_instructions = self.db.all( "SELECT * FROM payment_instructions ORDER BY participant ASC") assert len(payment_instructions) == 2 assert payment_instructions[0].participant == 'alice' assert payment_instructions[0].team == 'new_team' assert payment_instructions[0].amount == 1 assert payment_instructions[1].participant == 'bob' assert payment_instructions[1].team == 'new_team' assert payment_instructions[1].amount == 2 def test_migrate_tips_only_runs_once(self): alice = self.make_participant('alice', claimed_time='now') self.make_participant('old_team') self.make_tip(alice, 'old_team', '1.00') new_team = self.make_team('new_team', owner='old_team') new_team.migrate_tips() with pytest.raises(AlreadyMigrated): new_team.migrate_tips() payment_instructions = self.db.all( "SELECT * FROM payment_instructions") assert len(payment_instructions) == 1 def test_migrate_tips_checks_for_multiple_teams(self): alice = self.make_participant('alice', claimed_time='now') self.make_participant('old_team') self.make_tip(alice, 'old_team', '1.00') new_team = self.make_team('new_team', owner='old_team') new_team.migrate_tips() newer_team = self.make_team('newer_team', owner='old_team') with pytest.raises(AlreadyMigrated): newer_team.migrate_tips() payment_instructions = self.db.all( "SELECT * FROM payment_instructions") assert len(payment_instructions) == 1 # Dues, Upcoming Payment # ====================== def test_get_dues(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') bob = self.make_participant('bob', claimed_time='now', last_bill_result='Fail!') team = self.make_team(is_approved=True) alice.set_payment_instruction(team, '3.00') # Funded bob.set_payment_instruction(team, '5.00') # Unfunded # Simulate dues self.db.run("UPDATE payment_instructions SET due = amount") assert team.get_dues() == (3, 5) def test_upcoming_payment(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') bob = self.make_participant('bob', claimed_time='now', last_bill_result='') carl = self.make_participant('carl', claimed_time='now', last_bill_result='Fail!') team = self.make_team(is_approved=True) alice.set_payment_instruction(team, '5.00') # Funded bob.set_payment_instruction( team, '3.00') # Funded, but won't hit minimum charge carl.set_payment_instruction(team, '10.00') # Unfunded # Simulate dues self.db.run("UPDATE payment_instructions SET due = amount") assert team.get_upcoming_payment() == 10 # 2 * Alice's $5 # Cached Values # ============= def test_receiving_only_includes_funded_payment_instructions(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') bob = self.make_participant('bob', claimed_time='now', last_bill_result="Fail!") team = self.make_team(is_approved=True) alice.set_payment_instruction( team, '3.00') # The only funded payment instruction bob.set_payment_instruction(team, '5.00') assert team.receiving == Decimal('3.00') assert team.nreceiving_from == 1 funded_payment_instruction = self.db.one( "SELECT * FROM payment_instructions " "WHERE is_funded ORDER BY id") assert funded_payment_instruction.participant == alice.username def test_receiving_only_includes_latest_payment_instructions(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') team = self.make_team(is_approved=True) alice.set_payment_instruction(team, '5.00') alice.set_payment_instruction(team, '3.00') assert team.receiving == Decimal('3.00') assert team.nreceiving_from == 1 # Images # ====== def test_save_image_saves_image(self): team = self.make_team() team.save_image(IMAGE, IMAGE, IMAGE, 'image/png') media_type = self.db.one('SELECT image_type FROM teams WHERE id=%s', (team.id, )) assert media_type == 'image/png' def test_save_image_records_the_event(self): team = self.make_team() oids = team.save_image(IMAGE, IMAGE, IMAGE, 'image/png') event = self.db.one('SELECT * FROM events') assert event.payload == { 'action': 'upsert_image', 'original': oids['original'], 'large': oids['large'], 'small': oids['small'], 'id': team.id } def test_load_image_loads_image(self): team = self.make_team() team.save_image(IMAGE, IMAGE, IMAGE, 'image/png') image = team.load_image('large') # buffer assert str(image) == IMAGE def test_image_endpoint_serves_an_image(self): team = self.make_team() team.save_image(IMAGE, IMAGE, IMAGE, 'image/png') image = self.client.GET('/TheEnterprise/image').body # buffer assert str(image) == IMAGE # Update # ====== def test_update_works(self): team = self.make_team(slug='enterprise') update_data = { 'name': 'Enterprise', 'product_or_service': 'We save galaxies.', 'homepage': 'http://starwars-enterprise.com/', 'onboarding_url': 'http://starwars-enterprise.com/onboarding', 'todo_url': 'http://starwars-enterprise.com/todos', } team.update(**update_data) team = Team.from_slug('enterprise') for field in update_data: assert getattr(team, field) == update_data[field] def test_can_only_update_allowed_fields(self): allowed_fields = set([ 'name', 'product_or_service', 'homepage', 'onboarding_url', 'todo_url' ]) team = self.make_team(slug='enterprise') fields = vars(team).keys() for field in fields: if field not in allowed_fields: with pytest.raises(AssertionError): team.update(field='foo') def test_update_records_the_old_values_as_events(self): team = self.make_team(slug='enterprise', product_or_service='Product') team.update(name='Enterprise', product_or_service='We save galaxies.') event = self.db.one('SELECT * FROM events') assert event.payload == { 'action': 'update', 'id': team.id, 'name': 'The Enterprise', 'product_or_service': 'Product' } def test_update_updates_object_attributes(self): team = self.make_team(slug='enterprise') team.update(name='Enterprise', product_or_service='We save galaxies.') assert team.name == 'Enterprise' assert team.product_or_service == 'We save galaxies.' # slugize def test_slugize_slugizes(self): assert slugize('Foo') == 'Foo' def test_slugize_requires_a_letter(self): assert pytest.raises(InvalidTeamName, slugize, '123') def test_slugize_accepts_letter_in_middle(self): assert slugize('1a23') == '1a23' def test_slugize_converts_comma_to_dash(self): assert slugize('foo,bar') == 'foo-bar' def test_slugize_converts_space_to_dash(self): assert slugize('foo bar') == 'foo-bar' def test_slugize_allows_underscore(self): assert slugize('foo_bar') == 'foo_bar' def test_slugize_allows_period(self): assert slugize('foo.bar') == 'foo.bar' def test_slugize_trims_whitespace(self): assert slugize(' Foo Bar ') == 'Foo-Bar' def test_slugize_trims_dashes(self): assert slugize('--Foo Bar--') == 'Foo-Bar' def test_slugize_trims_replacement_dashes(self): assert slugize(',,Foo Bar,,') == 'Foo-Bar' def test_slugize_folds_dashes_together(self): assert slugize('1a----------------23') == '1a-23'
class TestTeams(Harness): valid_data = { 'name': 'Gratiteam', 'product_or_service': 'We make widgets.', 'homepage': 'http://gratipay.com/', 'onboarding_url': 'http://inside.gratipay.com/', 'todo_url': 'https://github.com/gratipay', 'agree_public': 'true', 'agree_payroll': 'true', 'agree_terms': 'true', 'image': FileUpload(IMAGE, 'logo.png'), } def post_new(self, data, auth_as='alice', expected=200): r = self.client.POST( '/teams/create.json' , data=data , auth_as=auth_as , raise_immediately=False ) assert r.code == expected return r def test_harness_can_make_a_team(self): team = self.make_team() assert team.name == 'The Enterprise' assert team.owner == 'picard' def test_can_construct_from_slug(self): self.make_team() team = Team.from_slug('TheEnterprise') assert team.name == 'The Enterprise' assert team.owner == 'picard' def test_can_construct_from_id(self): team = Team.from_id(self.make_team().id) assert team.name == 'The Enterprise' assert team.owner == 'picard' @mock.patch('gratipay.models.team.Team.create_github_review_issue') def test_can_create_new_team(self, cgri): cgri.return_value = REVIEW_URL self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='') r = self.post_new(dict(self.valid_data)) team = self.db.one("SELECT * FROM teams") assert team assert team.owner == 'alice' assert json.loads(r.body)['review_url'] == team.review_url def test_all_fields_persist(self): self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='') self.post_new(dict(self.valid_data)) team = Team.from_slug('gratiteam') assert team.name == 'Gratiteam' assert team.homepage == 'http://gratipay.com/' assert team.product_or_service == 'We make widgets.' fallback = 'https://github.com/gratipay/team-review/issues#error-401' assert team.review_url in (REVIEW_URL, fallback) def test_casing_of_urls_survives(self): self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='') self.post_new(dict( self.valid_data , homepage='Http://gratipay.com/' , onboarding_url='http://INSIDE.GRATipay.com/' , todo_url='hTTPS://github.com/GRATIPAY' )) team = Team.from_slug('gratiteam') assert team.homepage == 'Http://gratipay.com/' assert team.onboarding_url == 'http://INSIDE.GRATipay.com/' assert team.todo_url == 'hTTPS://github.com/GRATIPAY' def test_401_for_anon_creating_new_team(self): self.post_new(self.valid_data, auth_as=None, expected=401) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 def test_error_message_for_no_valid_email(self): self.make_participant('alice', claimed_time='now') r = self.post_new(dict(self.valid_data), expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "You must have a verified email address to apply for a new team." in r.body def test_error_message_for_no_payout_route(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**') r = self.post_new(dict(self.valid_data), expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "You must attach a PayPal account to apply for a new team." in r.body def test_error_message_for_public_review(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') data = dict(self.valid_data) del data['agree_public'] r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Sorry, you must agree to have your application publicly reviewed." in r.body def test_error_message_for_payroll(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') data = dict(self.valid_data) del data['agree_payroll'] r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Sorry, you must agree to be responsible for payroll." in r.body def test_error_message_for_terms(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') data = dict(self.valid_data) del data['agree_terms'] r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Sorry, you must agree to the terms of service." in r.body def test_error_message_for_missing_fields(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') data = dict(self.valid_data) del data['name'] r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Please fill out the 'Team Name' field." in r.body def test_error_message_for_bad_url(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') r = self.post_new(dict(self.valid_data, homepage='foo'), expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Please enter an http[s]:// URL for the 'Homepage' field." in r.body r = self.post_new(dict(self.valid_data, onboarding_url='foo'), expected=400) assert "an http[s]:// URL for the 'Self-onboarding Documentation URL' field." in r.body r = self.post_new(dict(self.valid_data, todo_url='foo'), expected=400) assert "Please enter an http[s]:// URL for the 'To-do URL' field." in r.body def test_error_message_for_slug_collision(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') self.post_new(dict(self.valid_data)) r = self.post_new(dict(self.valid_data), expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 1 assert "Sorry, there is already a team using 'gratiteam'." in r.body def test_approved_team_shows_up_on_homepage(self): self.make_team(is_approved=True) assert 'The Enterprise' in self.client.GET("/").body def test_unreviewed_team_shows_up_on_homepage(self): self.make_team(is_approved=None) assert 'The Enterprise' in self.client.GET("/").body def test_rejected_team_shows_up_on_homepage(self): self.make_team(is_approved=False) assert 'The Enterprise' in self.client.GET("/").body def test_stripping_required_inputs(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**', last_paypal_result='') data = dict(self.valid_data) data['name'] = " " r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Please fill out the 'Team Name' field." in r.body def test_migrate_tips_to_payment_instructions(self): alice = self.make_participant('alice', claimed_time='now') bob = self.make_participant('bob', claimed_time='now') self.make_participant('old_team') self.make_tip(alice, 'old_team', '1.00') self.make_tip(bob, 'old_team', '2.00') new_team = self.make_team('new_team', owner='old_team') ntips = new_team.migrate_tips() assert ntips == 2 payment_instructions = self.db.all("SELECT * FROM payment_instructions ORDER BY participant ASC") assert len(payment_instructions) == 2 assert payment_instructions[0].participant == 'alice' assert payment_instructions[0].team == 'new_team' assert payment_instructions[0].amount == 1 assert payment_instructions[1].participant == 'bob' assert payment_instructions[1].team == 'new_team' assert payment_instructions[1].amount == 2 def test_migrate_tips_only_runs_once(self): alice = self.make_participant('alice', claimed_time='now') self.make_participant('old_team') self.make_tip(alice, 'old_team', '1.00') new_team = self.make_team('new_team', owner='old_team') new_team.migrate_tips() with pytest.raises(AlreadyMigrated): new_team.migrate_tips() payment_instructions = self.db.all("SELECT * FROM payment_instructions") assert len(payment_instructions) == 1 def test_migrate_tips_checks_for_multiple_teams(self): alice = self.make_participant('alice', claimed_time='now') self.make_participant('old_team') self.make_tip(alice, 'old_team', '1.00') new_team = self.make_team('new_team', owner='old_team') new_team.migrate_tips() newer_team = self.make_team('newer_team', owner='old_team') with pytest.raises(AlreadyMigrated): newer_team.migrate_tips() payment_instructions = self.db.all("SELECT * FROM payment_instructions") assert len(payment_instructions) == 1 # cached values - receiving, nreceiving_from def test_receiving_only_includes_funded_payment_instructions(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') bob = self.make_participant('bob', claimed_time='now', last_bill_result="Fail!") team = self.make_team(is_approved=True) alice.set_payment_instruction(team, '3.00') # The only funded payment instruction bob.set_payment_instruction(team, '5.00') assert team.receiving == Decimal('3.00') assert team.nreceiving_from == 1 funded_payment_instruction = self.db.one("SELECT * FROM payment_instructions " "WHERE is_funded ORDER BY id") assert funded_payment_instruction.participant == alice.username def test_receiving_only_includes_latest_payment_instructions(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') team = self.make_team(is_approved=True) alice.set_payment_instruction(team, '5.00') alice.set_payment_instruction(team, '3.00') assert team.receiving == Decimal('3.00') assert team.nreceiving_from == 1 # Images # ====== def test_save_image_saves_image(self): team = self.make_team() team.save_image(IMAGE, IMAGE, IMAGE, 'image/png') media_type = self.db.one('SELECT image_type FROM teams WHERE id=%s', (team.id,)) assert media_type == 'image/png' def test_save_image_records_the_event(self): team = self.make_team() oids = team.save_image(IMAGE, IMAGE, IMAGE, 'image/png') event = self.db.one('SELECT * FROM events') assert event.payload == { 'action': 'upsert_image' , 'original': oids['original'] , 'large': oids['large'] , 'small': oids['small'] , 'id': team.id } def test_load_image_loads_image(self): team = self.make_team() team.save_image(IMAGE, IMAGE, IMAGE, 'image/png') image = team.load_image('large') # buffer assert str(image) == IMAGE def test_image_endpoint_serves_an_image(self): team = self.make_team() team.save_image(IMAGE, IMAGE, IMAGE, 'image/png') image = self.client.GET('/TheEnterprise/image').body # buffer assert str(image) == IMAGE
class TestTeams(QueuedEmailHarness): valid_data = { 'name': 'Gratiteam', 'product_or_service': 'We make widgets.', 'homepage': 'http://gratipay.com/', 'onboarding_url': 'http://inside.gratipay.com/', 'agree_public': 'true', 'agree_payroll': 'true', 'agree_terms': 'true', 'image': FileUpload(IMAGE, 'logo.png'), } def post_new(self, data, auth_as='alice', expected=200): r = self.client.POST( '/teams/create.json' , data=data , auth_as=auth_as , raise_immediately=False ) assert r.code == expected return r def test_harness_can_make_a_team(self): team = self.make_team() assert team.name == 'The Enterprise' assert team.owner == 'picard' def test_can_construct_from_slug(self): self.make_team() team = Team.from_slug('TheEnterprise') assert team.name == 'The Enterprise' assert team.owner == 'picard' def test_can_construct_from_id(self): team = Team.from_id(self.make_team().id) assert team.name == 'The Enterprise' assert team.owner == 'picard' def make_alice(self): self.make_participant( 'alice' , claimed_time='now' , email_address='*****@*****.**' , last_paypal_result='' ) def test_can_create_new_team(self): self.make_alice() r = self.post_new(dict(self.valid_data)) team = self.db.one("SELECT * FROM teams") assert team assert team.owner == 'alice' assert json.loads(r.body)['review_url'] == team.review_url def test_all_fields_persist(self): self.make_alice() self.post_new(dict(self.valid_data)) team = T('gratiteam') assert team.name == 'Gratiteam' assert team.homepage == 'http://gratipay.com/' assert team.product_or_service == 'We make widgets.' assert team.review_url == 'some-github-issue' def test_casing_of_urls_survives(self): self.make_alice() self.post_new(dict( self.valid_data , homepage='Http://gratipay.com/' )) team = T('gratiteam') assert team.homepage == 'Http://gratipay.com/' def test_casing_of_slug_survives(self): self.make_alice() data = dict(self.valid_data) data['name'] = 'GratiTeam' self.post_new(dict(data)) team = T('GratiTeam') assert team is not None assert team.slug_lower == 'gratiteam' def test_application_email_sent_to_owner(self): self.make_alice() self.post_new(dict(self.valid_data)) last_email = self.get_last_email() self.app.email_queue.flush() assert last_email['to'] == 'alice <*****@*****.**>' expected = "Thanks for applying to use Gratipay!" assert expected in last_email['body_text'] def test_401_for_anon_creating_new_team(self): self.post_new(self.valid_data, auth_as=None, expected=401) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 def test_error_message_for_no_valid_email(self): self.make_participant('alice', claimed_time='now') r = self.post_new(dict(self.valid_data), expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "You must have a verified email address to apply for a new team." in r.body def test_error_message_for_no_payout_route(self): self.make_participant('alice', claimed_time='now', email_address='*****@*****.**') r = self.post_new(dict(self.valid_data), expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "You must attach a PayPal account to apply for a new team." in r.body def test_error_message_for_public_review(self): self.make_alice() data = dict(self.valid_data) del data['agree_public'] r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Sorry, you must agree to have your application publicly reviewed." in r.body def test_error_message_for_terms(self): self.make_alice() data = dict(self.valid_data) del data['agree_terms'] r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Sorry, you must agree to the terms of service." in r.body def test_error_message_for_missing_fields(self): self.make_alice() data = dict(self.valid_data) del data['name'] r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Please fill out the 'Team Name' field." in r.body def test_error_message_for_bad_url(self): self.make_alice() r = self.post_new(dict(self.valid_data, homepage='foo'), expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Please enter an http[s]:// URL for the 'Homepage' field." in r.body def test_error_message_for_invalid_team_name(self): self.make_alice() data = dict(self.valid_data) data['name'] = '~Invalid:Name;' r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Sorry, your team name is invalid." in r.body def test_error_message_for_slug_collision(self): self.make_alice() self.post_new(dict(self.valid_data)) r = self.post_new(dict(self.valid_data), expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 1 assert "Sorry, there is already a team using 'Gratiteam'." in r.body def test_stripping_required_inputs(self): self.make_alice() data = dict(self.valid_data) data['name'] = " " r = self.post_new(data, expected=400) assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Please fill out the 'Team Name' field." in r.body def test_receiving_page_basically_works(self): team = self.make_team(is_approved=True) alice = self.make_participant('alice', claimed_time='now', last_bill_result='') alice.set_payment_instruction(team, '3.00') body = self.client.GET('/TheEnterprise/receiving/', auth_as='picard').body assert '100.0%' in body # Dues, Upcoming Payment # ====================== def test_get_dues(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') bob = self.make_participant('bob', claimed_time='now', last_bill_result='Fail!') team = self.make_team(is_approved=True) alice.set_payment_instruction(team, '3.00') # Funded bob.set_payment_instruction(team, '5.00') # Unfunded # Simulate dues self.db.run("UPDATE payment_instructions SET due = amount") assert team.get_dues() == (3, 5) def test_upcoming_payment(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') bob = self.make_participant('bob', claimed_time='now', last_bill_result='') carl = self.make_participant('carl', claimed_time='now', last_bill_result='Fail!') team = self.make_team(is_approved=True) alice.set_payment_instruction(team, '5.00') # Funded bob.set_payment_instruction(team, '3.00') # Funded, but won't hit minimum charge carl.set_payment_instruction(team, '10.00') # Unfunded # Simulate dues self.db.run("UPDATE payment_instructions SET due = amount") assert team.get_upcoming_payment() == 10 # 2 * Alice's $5 # Cached Values # ============= def test_receiving_only_includes_funded_payment_instructions(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') bob = self.make_participant('bob', claimed_time='now', last_bill_result="Fail!") team = self.make_team(is_approved=True) alice.set_payment_instruction(team, '3.00') # The only funded payment instruction bob.set_payment_instruction(team, '5.00') assert team.receiving == D('3.00') assert team.nreceiving_from == 1 funded_payment_instruction = self.db.one("SELECT * FROM payment_instructions " "WHERE is_funded ORDER BY id") assert funded_payment_instruction.participant_id == alice.id def test_receiving_only_includes_latest_payment_instructions(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') team = self.make_team(is_approved=True) alice.set_payment_instruction(team, '5.00') alice.set_payment_instruction(team, '3.00') assert team.receiving == D('3.00') assert team.nreceiving_from == 1 # Images # ====== def test_save_image_saves_image(self): team = self.make_team() team.save_image(IMAGE, IMAGE, IMAGE, 'image/png') media_type = self.db.one('SELECT image_type FROM teams WHERE id=%s', (team.id,)) assert media_type == 'image/png' def test_save_image_records_the_event(self): team = self.make_team() oids = team.save_image(IMAGE, IMAGE, IMAGE, 'image/png') event = self.db.all('SELECT * FROM events ORDER BY ts DESC')[0] assert event.type == 'team' assert event.payload == { 'action': 'upsert_image' , 'original': oids['original'] , 'large': oids['large'] , 'small': oids['small'] , 'id': team.id } def test_load_image_loads_image(self): team = self.make_team() team.save_image(IMAGE, IMAGE, IMAGE, 'image/png') image = team.load_image('large') # buffer assert str(image) == IMAGE def test_image_endpoint_serves_an_image(self): team = self.make_team() team.save_image(IMAGE, IMAGE, IMAGE, 'image/png') image = self.client.GET('/TheEnterprise/image').body # buffer assert str(image) == IMAGE def test_get_image_url_gets_image_url(self): team = self.make_team() team.save_image(IMAGE, IMAGE, IMAGE, 'image/png') assert team.get_image_url('small') == '/TheEnterprise/image?size=small' # Update # ====== def test_update_works(self): team = self.make_team(slug='enterprise') update_data = { 'name': 'Enterprise', 'product_or_service': 'We save galaxies.', 'homepage': 'http://starwars-enterprise.com/', 'onboarding_url': 'http://starwars-enterprise.com/onboarding', } team.update(**update_data) team = T('enterprise') for field in update_data: assert getattr(team, field) == update_data[field] def test_can_only_update_allowed_fields(self): allowed_fields = set(['name', 'product_or_service', 'homepage', 'onboarding_url',]) team = self.make_team(slug='enterprise') fields = vars(team).keys() for field in fields: if field not in allowed_fields: with pytest.raises(AssertionError): team.update(field='foo') def test_homepage_not_allowed_for_package(self): alice = self.make_participant('alice', claimed_time='now') package = self.make_package(name='enterprise') with self.db.get_cursor() as c: team = package.get_or_create_linked_team(c, alice) pytest.raises(AssertionError, team.update, homepage='foo') def test_update_records_the_old_values_as_events(self): team = self.make_team(slug='enterprise', product_or_service='Product') team.update(name='Enterprise', product_or_service='We save galaxies.') event = self.db.all('SELECT * FROM events ORDER BY ts DESC')[0] assert event.payload == { 'action': 'update' , 'id': team.id , 'name': 'The Enterprise' , 'product_or_service': 'Product' } def test_update_updates_object_attributes(self): team = self.make_team(slug='enterprise') team.update(name='Enterprise', product_or_service='We save galaxies.') assert team.name == 'Enterprise' assert team.product_or_service == 'We save galaxies.' # slugize def test_slugize_slugizes(self): assert slugize('Foo') == 'Foo' def test_slugize_requires_a_letter(self): assert pytest.raises(InvalidTeamName, slugize, '123') def test_slugize_accepts_letter_in_middle(self): assert slugize('1a23') == '1a23' def test_slugize_converts_comma_to_dash(self): assert slugize('foo,bar') == 'foo-bar' def test_slugize_converts_space_to_dash(self): assert slugize('foo bar') == 'foo-bar' def test_slugize_allows_underscore(self): assert slugize('foo_bar') == 'foo_bar' def test_slugize_allows_period(self): assert slugize('foo.bar') == 'foo.bar' def test_slugize_trims_whitespace(self): assert slugize(' Foo Bar ') == 'Foo-Bar' def test_slugize_trims_dashes(self): assert slugize('--Foo Bar--') == 'Foo-Bar' def test_slugize_trims_replacement_dashes(self): assert slugize(',,Foo Bar,,') == 'Foo-Bar' def test_slugize_folds_dashes_together(self): assert slugize('1a----------------23') == '1a-23' def test_slugize_disallows_slashes(self): self.assertRaises(InvalidTeamName, slugize, 'abc/def') def test_slugize_disallows_questions(self): self.assertRaises(InvalidTeamName, slugize, 'abc?def') def test_slugize_disallows_backslashes(self): self.assertRaises(InvalidTeamName, slugize, 'abc\def')