From 713fa95737ebe6df3e8b71133715b9871ae00cb5 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Sat, 30 Apr 2016 18:37:59 -0400 Subject: [PATCH] implement has_verified_identity --- sql/branch.sql | 32 +++++++++ tests/py/test_participant_identities.py | 94 +++++++++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/sql/branch.sql b/sql/branch.sql index 77a948ac66..1c13a1c7e4 100644 --- a/sql/branch.sql +++ b/sql/branch.sql @@ -16,3 +16,35 @@ CREATE TABLE participant_identities , is_verified boolean NOT NULL DEFAULT false , UNIQUE(participant_id, country_id) ); + + +-- participants.has_verified_identity + +ALTER TABLE participants ADD COLUMN has_verified_identity bool NOT NULL DEFAULT false; + +CREATE FUNCTION update_has_verified_identity() RETURNS trigger AS $$ + BEGIN + UPDATE participants p + SET has_verified_identity=COALESCE(( + SELECT is_verified + FROM participant_identities + WHERE participant_id = OLD.participant_id + AND is_verified + LIMIT 1 + ), false) + WHERE p.id = OLD.participant_id; + RETURN NULL; + END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER propagate_is_verified_changes + AFTER UPDATE OF is_verified ON participant_identities + FOR EACH ROW + EXECUTE PROCEDURE update_has_verified_identity(); + +CREATE TRIGGER propagate_is_verified_removal + AFTER DELETE ON participant_identities + FOR EACH ROW + EXECUTE PROCEDURE update_has_verified_identity(); + +-- We don't need an INSERT trigger, because of the way the defaults play out. diff --git a/tests/py/test_participant_identities.py b/tests/py/test_participant_identities.py index 0bdebdd441..1ccfb3b480 100644 --- a/tests/py/test_participant_identities.py +++ b/tests/py/test_participant_identities.py @@ -1,10 +1,12 @@ from __future__ import absolute_import, division, print_function, unicode_literals from gratipay.testing import Harness +from gratipay.models.participant import Participant from gratipay.models.participant.mixins import identity, Identity from gratipay.models.participant.mixins.identity import _validate_info from gratipay.models.participant.mixins.identity import ParticipantIdentityInfoInvalid from gratipay.models.participant.mixins.identity import ParticipantIdentitySchemaUnknown +from postgres.orm import ReadOnly from pytest import raises @@ -256,3 +258,95 @@ def test_ci_still_logs_an_event_when_noop(self): crusher = self.make_participant('crusher') crusher.clear_identity(self.TTO) self.assert_events(crusher.id, [None], [self.TTO], ['clear identity']) + + + # hvi - has_verified_identity + + def test_hvi_defaults_to_false(self): + crusher = self.make_participant('crusher') + assert crusher.has_verified_identity is False + + def test_hvi_is_read_only(self): + crusher = self.make_participant('crusher') + with raises(ReadOnly): + crusher.has_verified_identity = True + + def test_hvi_becomes_true_when_an_identity_is_verified(self): + crusher = self.make_participant('crusher') + crusher.store_identity_info(self.TTO, 'nothing-enforced', {}) + crusher.set_identity_verification(self.TTO, True) + assert Participant.from_username('crusher').has_verified_identity + + def test_hvi_becomes_false_when_the_identity_is_unverified(self): + crusher = self.make_participant('crusher') + crusher.store_identity_info(self.TTO, 'nothing-enforced', {}) + crusher.set_identity_verification(self.TTO, True) + crusher.set_identity_verification(self.TTO, False) + assert not Participant.from_username('crusher').has_verified_identity + + def test_hvi_stays_true_when_a_secondary_identity_is_verified(self): + crusher = self.make_participant('crusher') + crusher.store_identity_info(self.USA, 'nothing-enforced', {}) + crusher.set_identity_verification(self.USA, True) + crusher.store_identity_info(self.TTO, 'nothing-enforced', {}) + crusher.set_identity_verification(self.TTO, True) + assert Participant.from_username('crusher').has_verified_identity + + def test_hvi_stays_true_when_the_secondary_identity_is_unverified(self): + crusher = self.make_participant('crusher') + crusher.store_identity_info(self.USA, 'nothing-enforced', {}) + crusher.set_identity_verification(self.USA, True) + crusher.store_identity_info(self.TTO, 'nothing-enforced', {}) + crusher.set_identity_verification(self.TTO, True) + crusher.set_identity_verification(self.TTO, False) + assert Participant.from_username('crusher').has_verified_identity + + def test_hvi_goes_back_to_false_when_both_are_unverified(self): + crusher = self.make_participant('crusher') + crusher.store_identity_info(self.USA, 'nothing-enforced', {}) + crusher.store_identity_info(self.TTO, 'nothing-enforced', {}) + crusher.set_identity_verification(self.TTO, True) + crusher.set_identity_verification(self.USA, True) + crusher.set_identity_verification(self.TTO, False) + crusher.set_identity_verification(self.USA, False) + assert not Participant.from_username('crusher').has_verified_identity + + def test_hvi_changes_are_scoped_to_a_participant(self): + crusher = self.make_participant('crusher') + crusher.store_identity_info(self.USA, 'nothing-enforced', {}) + + bruiser = self.make_participant('bruiser') + bruiser.store_identity_info(self.USA, 'nothing-enforced', {}) + + crusher.set_identity_verification(self.USA, True) + + assert Participant.from_username('crusher').has_verified_identity + assert not Participant.from_username('bruiser').has_verified_identity + + def test_hvi_resets_when_identity_is_cleared(self): + crusher = self.make_participant('crusher') + crusher.store_identity_info(self.TTO, 'nothing-enforced', {}) + crusher.set_identity_verification(self.TTO, True) + crusher.clear_identity(self.TTO) + assert not Participant.from_username('crusher').has_verified_identity + + def test_hvi_doesnt_reset_when_penultimate_identity_is_cleared(self): + crusher = self.make_participant('crusher') + crusher.store_identity_info(self.USA, 'nothing-enforced', {}) + crusher.set_identity_verification(self.USA, True) + crusher.store_identity_info(self.TTO, 'nothing-enforced', {}) + crusher.set_identity_verification(self.TTO, True) + crusher.set_identity_verification(self.TTO, False) + crusher.clear_identity(self.TTO) + assert Participant.from_username('crusher').has_verified_identity + + def test_hvi_does_reset_when_both_identities_are_cleared(self): + crusher = self.make_participant('crusher') + crusher.store_identity_info(self.USA, 'nothing-enforced', {}) + crusher.store_identity_info(self.TTO, 'nothing-enforced', {}) + crusher.set_identity_verification(self.USA, True) + crusher.set_identity_verification(self.TTO, True) + crusher.set_identity_verification(self.TTO, False) + crusher.set_identity_verification(self.USA, False) + crusher.clear_identity(self.TTO) + assert not Participant.from_username('crusher').has_verified_identity