From 01f81ce4d5f81ac5063dfe909e6ccbd39237801d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 21 Dec 2017 12:59:32 +0100 Subject: [PATCH] Send a Blinker signal when someone's subscription status changes This is very close to the 'roles changed' signal, with the difference that it is sent only once for multiple role changes. --- pillar/api/blender_cloud/subscription.py | 13 ++++++++++ tests/test_api/test_subscriptions.py | 30 ++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/pillar/api/blender_cloud/subscription.py b/pillar/api/blender_cloud/subscription.py index ef906579..569d6d13 100644 --- a/pillar/api/blender_cloud/subscription.py +++ b/pillar/api/blender_cloud/subscription.py @@ -1,6 +1,7 @@ import logging import typing +import blinker from flask import Blueprint, Response import requests from requests.adapters import HTTPAdapter @@ -21,6 +22,10 @@ ROLES_BID_TO_PILLAR = { 'cloud_has_subscription': 'has_subscription', } +user_subscription_updated = blinker.NamedSignal( + 'user_subscription_updated', + 'The sender is a UserClass instance, kwargs includes "revoke_roles" and "grant_roles".') + @blueprint.route('/update-subscription') @authorization.require_login() @@ -157,6 +162,14 @@ def do_update_subscription(local_user: auth.UserClass, bid_user: dict): user_id, email, ', '.join(sorted(revoke_roles))) service.do_badger('revoke', roles=revoke_roles, user_id=user_id) + # Let the world know this user's subscription was updated. + final_roles = (plr_roles - revoke_roles).union(grant_roles) + local_user.roles = list(final_roles) + local_user.collect_capabilities() + user_subscription_updated.send(local_user, + grant_roles=grant_roles, + revoke_roles=revoke_roles) + # Re-index the user in the search database. from pillar.api.users import hooks hooks.push_updated_user_to_algolia({'_id': user_id}, {}) diff --git a/tests/test_api/test_subscriptions.py b/tests/test_api/test_subscriptions.py index 08dec044..e5b4a3c0 100644 --- a/tests/test_api/test_subscriptions.py +++ b/tests/test_api/test_subscriptions.py @@ -17,6 +17,13 @@ class RoleUpdatingTest(AbstractPillarTest): with self.app.test_request_context(): self.create_standard_groups() + from pillar.api.blender_cloud import subscription as sub + self.user_subs_signal_calls = [] + sub.user_subscription_updated.connect(self._user_subs_signal) + + def _user_subs_signal(self, sender, **kwargs): + self.user_subs_signal_calls.append((sender, kwargs)) + def _setup_testcase(self, mocked_fetch_blenderid_user, *, bid_roles: typing.Set[str]): import urllib.parse @@ -54,6 +61,12 @@ class RoleUpdatingTest(AbstractPillarTest): user_info = self.get('/api/users/me', auth_token='my-happy-token').json() self.assertEqual({'subscriber', 'has_subscription'}, set(user_info['roles'])) + # Check the signals + self.assertEqual(1, len(self.user_subs_signal_calls)) + sender, kwargs = self.user_subs_signal_calls[0] + self.assertEqual({'revoke_roles': set(), 'grant_roles': {'subscriber', 'has_subscription'}}, + kwargs) + @responses.activate @mock.patch('pillar.api.blender_id.fetch_blenderid_user') def test_store_api_role_revoke_subscriber(self, mocked_fetch_blenderid_user): @@ -61,9 +74,9 @@ class RoleUpdatingTest(AbstractPillarTest): bid_roles={'conference_speaker'}) # Make sure this user is currently known as a subcriber. - self.create_user(roles={'subscriber'}, token='my-happy-token') + self.create_user(roles={'subscriber', 'has_subscription'}, token='my-happy-token') user_info = self.get('/api/users/me', auth_token='my-happy-token').json() - self.assertEqual(['subscriber'], user_info['roles']) + self.assertEqual({'subscriber', 'has_subscription'}, set(user_info['roles'])) # And after updating, it shouldn't be. self.get('/api/bcloud/update-subscription', auth_token='my-happy-token', @@ -71,6 +84,11 @@ class RoleUpdatingTest(AbstractPillarTest): user_info = self.get('/api/users/me', auth_token='my-happy-token').json() self.assertEqual([], user_info['roles']) + self.assertEqual(1, len(self.user_subs_signal_calls)) + sender, kwargs = self.user_subs_signal_calls[0] + self.assertEqual({'revoke_roles': {'subscriber', 'has_subscription'}, 'grant_roles': set()}, + kwargs) + @responses.activate @mock.patch('pillar.api.blender_id.fetch_blenderid_user') def test_bid_api_grant_demo(self, mocked_fetch_blenderid_user): @@ -83,6 +101,10 @@ class RoleUpdatingTest(AbstractPillarTest): user_info = self.get('/api/users/me', auth_token='my-happy-token').json() self.assertEqual(['demo'], user_info['roles']) + self.assertEqual(1, len(self.user_subs_signal_calls)) + sender, kwargs = self.user_subs_signal_calls[0] + self.assertEqual({'revoke_roles': set(), 'grant_roles': {'demo'}}, kwargs) + @responses.activate @mock.patch('pillar.api.blender_id.fetch_blenderid_user') def test_bid_api_role_revoke_demo(self, mocked_fetch_blenderid_user): @@ -99,3 +121,7 @@ class RoleUpdatingTest(AbstractPillarTest): expected_status=204) user_info = self.get('/api/users/me', auth_token='my-happy-token').json() self.assertEqual([], user_info['roles']) + + self.assertEqual(1, len(self.user_subs_signal_calls)) + sender, kwargs = self.user_subs_signal_calls[0] + self.assertEqual({'revoke_roles': {'demo'}, 'grant_roles': set()}, kwargs)