def check(self, *package_names): package_names, post = package_names[:-1], package_names[-1] post.return_value = 'some-github-url' nonce = self.start(self.address, *package_names) result = self.alice.finish_email_verification(self.address, nonce) # email? packages = [Package.from_names(NPM, name) for name in package_names] assert result == (_email.VERIFICATION_SUCCEEDED, packages, True if packages else None) assert self.alice.email_address == P( 'alice').email_address == self.address # database? for name in package_names: package = Package.from_names(NPM, name) assert package.team.package == package assert package.team.review_url == 'some-github-url' # GitHub issue? npackages = len(package_names) if npackages == 0: assert not post.called else: assert post.call_count == 1 posted = json.loads(post.mock_calls[0][1][0]) if npackages == 1: assert posted['title'] == 'foo' assert 'for at least a week' in posted['body'] else: assert posted['title'] == 'bar and foo' assert 'for at least a week' in posted['body'] assert self.db.all('select review_url from teams' ) == ['some-github-url'] * npackages
def check(self, *package_names): package_names, post = package_names[:-1], package_names[-1] post.return_value = 'some-github-url' nonce = self.start(self.address, *package_names) result = self.alice.finish_email_verification(self.address, nonce) # email? packages = [Package.from_names(NPM, name) for name in package_names] assert result == (_email.VERIFICATION_SUCCEEDED, packages, True if packages else None) assert self.alice.email_address == P('alice').email_address == self.address # database? for name in package_names: package = Package.from_names(NPM, name) assert package.team.package == package assert package.team.review_url == 'some-github-url' # GitHub issue? npackages = len(package_names) if npackages == 0: assert not post.called else: assert post.call_count == 1 posted = json.loads(post.mock_calls[0][1][0]) if npackages == 1: assert posted['title'] == 'foo' assert 'for at least a week' in posted['body'] else: assert posted['title'] == 'bar and foo' assert 'for at least a week' in posted['body'] assert self.db.all('select review_url from teams') == ['some-github-url'] * npackages
def test_deleting_package_removes_open_claims(self): self.add_and_verify_email(self.alice, self.address) self.alice.set_paypal_address(self.address) self.start(self.address, 'foo') load = lambda: self.db.one('select * from claims') assert load() is not None Package.from_names('npm', 'foo').delete() assert load() is None
def test_deleting_package_removes_open_claims(self): self.add_and_verify_email(self.alice, self.address) self.alice.set_paypal_address(self.address) self.start(self.address, 'foo') _load = lambda: self.db.one('select * from claims') assert _load() is not None Package.from_names('npm', 'foo').delete() assert _load() is None
def test_while_we_are_at_it_that_packages_have_unique_teams_that_survive_comparison(self): self.test_verified_address_and_multiple_packages_succeeds() foo = Package.from_names('npm', 'foo') bar = Package.from_names('npm', 'bar') assert foo.team == foo.team assert bar.team == bar.team assert foo.team != bar.team
def test_while_we_are_at_it_that_packages_have_unique_teams_that_survive_comparison( self): self.test_verified_address_and_multiple_packages_succeeds() foo = Package.from_names('npm', 'foo') bar = Package.from_names('npm', 'bar') assert foo.team == foo.team assert bar.team == bar.team assert foo.team != bar.team
def test_finishing_email_verification_with_preexisting_paypal_doesnt_update_paypal(self): self.add_and_verify_email(self.alice, self.address) self.alice.set_paypal_address(self.address) nonce = self.start(self.address, 'foo') result = self.alice.finish_email_verification(self.address, nonce) foo = Package.from_names('npm', 'foo') assert result == (_email.VERIFICATION_SUCCEEDED, [foo], False)
def consume_change_stream(stream, db): """Given an iterable of CouchDB change notifications and a :py:class:`~GratipayDB`, read from the stream and write to the db. The npm registry is a CouchDB app, which means we get a change stream from it that allows us to follow registry updates in near-realtime. Our strategy here is to maintain open connections to both the registry and our own database, and write as we read. """ with db.get_connection() as connection: for change in stream: # Decide what to do. if change.get('deleted'): package = Package.from_names(NPM, change['id']) if not package: # As a result of CouchDB's compaction algorithm, we might # receive 'deleted' events for docs even if we haven't seen # the corresponding events for when the doc was created continue op, kw = package.delete, {} else: op = Package.upsert kw = process_doc(change['doc']) if not kw: continue kw['package_manager'] = NPM # Do it. cursor = connection.cursor() kw['cursor'] = cursor op(**kw) cursor.run('UPDATE worker_coordination SET npm_last_seq=%(seq)s', change) connection.commit()
def claim_package(self, participant, package): """Given a participant and a package, claim the package for the participant. """ if participant.__class__ is not Participant: participant = P(participant) if package.__class__ is not Package: package = Package.from_names(NPM, package) package.get_or_create_linked_team(self.db, participant)
def test_finishing_email_verification_with_preexisting_paypal_doesnt_update_paypal( self): self.add_and_verify_email(self.alice, self.address) self.alice.set_paypal_address(self.address) nonce = self.start(self.address, 'foo') result = self.alice.finish_email_verification(self.address, nonce) foo = Package.from_names('npm', 'foo') assert result == (_email.VERIFICATION_SUCCEEDED, [foo], False)
def claim_package(self): foo = Package.from_names('npm', 'foo') alice = self.make_participant('alice', claimed_time='now') alice.start_email_verification('*****@*****.**', foo) nonce = alice.get_email('*****@*****.**').nonce alice.finish_email_verification('*****@*****.**', nonce) team = alice.get_teams()[0] assert team.package == foo return team.slug
def test_but_once_claimed_then_team_persists(self): self.make_package() self.check() foo = Package.from_names(NPM, 'foo') assert self.finish_claiming() == (email.VERIFICATION_SUCCEEDED, [foo], True) foo.delete() self.visit('/foo/') foo = self.css('#content .statement p') assert foo.text == 'Foo fooingly.'
def make_package(self, package_manager=NPM, name='foo', description='Foo fooingly.', emails=['*****@*****.**'], claimed_by=None): """Factory for packages. """ self.db.run( 'INSERT INTO packages (package_manager, name, description, emails) ' 'VALUES (%s, %s, %s, %s) RETURNING *' , (package_manager, name, description, emails) ) package = Package.from_names(NPM, name) if claimed_by: # either a username (existing or not) or a Participant object if type(claimed_by) is unicode: maybe = P(claimed_by) claimed_by = maybe if maybe is not None else self.make_owner(claimed_by) admin = self.make_admin() team = package.get_or_create_linked_team(self.db, claimed_by) team.update_review_status('approved', admin) return package
def consume_change_stream(stream, db): """Given an iterable of CouchDB change notifications and a :py:class:`~GratipayDB`, read from the stream and write to the db. The npm registry is a CouchDB app, which means we get a change stream from it that allows us to follow registry updates in near-realtime. Our strategy here is to maintain open connections to both the registry and our own database, and write as we read. """ with db.get_connection() as connection: for change in stream: # Decide what to do. if change.get('deleted'): package = Package.from_names(NPM, change['id']) if not package: # As a result of CouchDB's compaction algorithm, we might # receive 'deleted' events for docs even if we haven't seen # the corresponding events for when the doc was created continue op, kw = package.delete, {} else: op = Package.upsert doc = change.get('doc') if not doc: continue # We've seen this in the wild. kw = process_doc(doc) if not kw: continue kw['package_manager'] = NPM # Do it. cursor = connection.cursor() kw['cursor'] = cursor op(**kw) cursor.run('UPDATE worker_coordination SET npm_last_seq=%(seq)s', change) connection.commit()
def test_claiming_deleted_packages_is_a_noop(self): self.make_package() self.check() Package.from_names(NPM, 'foo').delete() assert self.finish_claiming() == (email.VERIFICATION_SUCCEEDED, [], None)
def test_deleted_packages_are_404(self): self.make_package() Package.from_names(NPM, 'foo').delete() self.visit('/on/npm/foo') assert self.css('#content h1').text == '404'
def test_from_names_can_use_a_cursor(self): self.make_package() with self.db.get_cursor() as cursor: assert Package.from_names(NPM, 'foo', cursor).name == 'foo'
def test_can_be_instantiated_from_names(self): self.make_package() assert Package.from_names(NPM, 'foo').name == 'foo'
def test_closing_team_unlinks_package(self): _, _, team = self.link() team.close() assert Package.from_names('npm', 'foo').team is None
# -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function, unicode_literals import mock from gratipay.models.package import Package, NPM from gratipay.models.package.emails import PRIMARY, VERIFIED, UNVERIFIED, UNLINKED, OTHER from gratipay.testing import Harness from psycopg2 import IntegrityError from pytest import raises Foo = lambda cursor=None: Package.from_names(NPM, 'foo', cursor) class Basics(Harness): def test_can_be_instantiated_from_id(self): package = self.make_package() assert Package.from_id(package.id).id == package.id def test_from_id_can_use_a_cursor(self): package = self.make_package() with self.db.get_cursor() as cursor: assert Package.from_id(package.id, cursor).id == package.id def test_can_be_instantiated_from_names(self): self.make_package() assert Package.from_names(NPM, 'foo').name == 'foo' def test_from_names_can_use_a_cursor(self): self.make_package()