This adds a new Blinker signal `user_logged_in` that is only sent when the user logs in via the web interface (and not on every token authentication and every API call).
276 lines
11 KiB
Python
276 lines
11 KiB
Python
import datetime
|
|
|
|
import requests
|
|
import responses
|
|
|
|
from pillar.tests import AbstractPillarTest
|
|
|
|
httpmock = responses.RequestsMock()
|
|
|
|
|
|
class AbstractSyncTest(AbstractPillarTest):
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.uid1 = self.create_user(24 * '1')
|
|
self.uid2 = self.create_user(24 * '2')
|
|
|
|
# Make sure the users have different auth info.
|
|
with self.app.app_context():
|
|
users_coll = self.app.db('users')
|
|
users_coll.update_one(
|
|
{'_id': self.uid1},
|
|
{'$set': {'auth': [
|
|
{'provider': 'local', 'user_id': '47', 'token': ''},
|
|
{'provider': 'blender-id', 'user_id': '1947', 'token': ''},
|
|
]}})
|
|
users_coll.update_one(
|
|
{'_id': self.uid2},
|
|
{'$set': {'auth': [
|
|
{'provider': 'blender-id', 'user_id': '4488', 'token': ''},
|
|
{'provider': 'local', 'user_id': '48', 'token': ''},
|
|
]}})
|
|
|
|
self.create_valid_auth_token(self.uid1, token='find-this-token-uid1',
|
|
oauth_scopes=['email', 'badge'])
|
|
self.create_valid_auth_token(self.uid1, token='no-badge-scope',
|
|
oauth_scopes=['email'])
|
|
self.create_valid_auth_token(self.uid1, token='expired',
|
|
oauth_scopes=['email', 'badge'],
|
|
expire_in_days=-1)
|
|
|
|
self.create_valid_auth_token(self.uid2, token='find-this-token-uid2',
|
|
oauth_scopes=['email', 'badge'])
|
|
self.create_valid_auth_token(self.uid2, token='no-badge-scope',
|
|
oauth_scopes=['email'])
|
|
self.create_valid_auth_token(self.uid2, token='expired',
|
|
oauth_scopes=['email', 'badge'],
|
|
expire_in_days=-1)
|
|
|
|
from pillar import badge_sync
|
|
self.sync_user1 = badge_sync.SyncUser(self.uid1, 'find-this-token-uid1', '1947')
|
|
self.sync_user2 = badge_sync.SyncUser(self.uid2, 'find-this-token-uid2', '4488')
|
|
|
|
def _update_badge_expiry(self, delta_minutes1, delta_minutes2):
|
|
"""Make badges of userN expire in delta_minutesN minutes."""
|
|
from pillar.api.utils import utcnow, remove_private_keys
|
|
now = utcnow()
|
|
|
|
# Do the update via Eve so that that flow is covered too.
|
|
with self.app.app_context():
|
|
users_coll = self.app.db('users')
|
|
db_user1 = users_coll.find_one(self.uid1)
|
|
db_user1['badges'] = {
|
|
'html': 'badge for user 1',
|
|
'expires': now + datetime.timedelta(minutes=delta_minutes1)
|
|
}
|
|
r, _, _, status = self.app.put_internal('users',
|
|
remove_private_keys(db_user1),
|
|
_id=self.uid1)
|
|
self.assertEqual(200, status, r)
|
|
|
|
with self.app.app_context():
|
|
db_user2 = users_coll.find_one(self.uid2)
|
|
db_user2['badges'] = {
|
|
'html': 'badge for user 2',
|
|
'expires': now + datetime.timedelta(minutes=delta_minutes2)
|
|
}
|
|
r, _, _, status = self.app.put_internal('users',
|
|
remove_private_keys(db_user2),
|
|
_id=self.uid2)
|
|
self.assertEqual(200, status, r)
|
|
|
|
|
|
class FindUsersToSyncTest(AbstractSyncTest):
|
|
def test_no_badge_fetched_yet(self):
|
|
from pillar import badge_sync
|
|
with self.app.app_context():
|
|
found = set(badge_sync.find_users_to_sync())
|
|
self.assertEqual({self.sync_user1, self.sync_user2}, found)
|
|
|
|
def test_badge_fetched_recently(self):
|
|
from pillar import badge_sync
|
|
|
|
# Badges of user1 expired, user2 didn't yet.
|
|
with self.app.app_context():
|
|
self._update_badge_expiry(-5, 5)
|
|
found = list(badge_sync.find_users_to_sync())
|
|
self.assertEqual([self.sync_user1], found)
|
|
|
|
# Badges of both users expired, but user2 expired longer ago.
|
|
with self.app.app_context():
|
|
self._update_badge_expiry(-5, -10)
|
|
found = list(badge_sync.find_users_to_sync())
|
|
self.assertEqual([self.sync_user2, self.sync_user1], found)
|
|
|
|
# Badges of both not expired yet.
|
|
with self.app.app_context():
|
|
self._update_badge_expiry(2, 3)
|
|
found = list(badge_sync.find_users_to_sync())
|
|
self.assertEqual([], found)
|
|
|
|
|
|
class FindUserSyncInfoTest(AbstractSyncTest):
|
|
def test_no_badge_fetched_yet(self):
|
|
from pillar import badge_sync
|
|
|
|
with self.app.app_context():
|
|
found1 = badge_sync.find_user_to_sync(self.uid1)
|
|
found2 = badge_sync.find_user_to_sync(self.uid2)
|
|
self.assertEqual(self.sync_user1, found1)
|
|
self.assertEqual(self.sync_user2, found2)
|
|
|
|
def test_badge_fetched_recently(self):
|
|
# This should be the same for all cases, as a single user
|
|
# is always refreshed.
|
|
from pillar import badge_sync
|
|
|
|
# Badges of user1 expired, user2 didn't yet.
|
|
with self.app.app_context():
|
|
self._update_badge_expiry(-5, 5)
|
|
found1 = badge_sync.find_user_to_sync(self.uid1)
|
|
found2 = badge_sync.find_user_to_sync(self.uid2)
|
|
self.assertEqual(self.sync_user1, found1)
|
|
self.assertEqual(self.sync_user2, found2)
|
|
|
|
# Badges of both users expired, but user2 expired longer ago.
|
|
with self.app.app_context():
|
|
self._update_badge_expiry(-5, -10)
|
|
found1 = badge_sync.find_user_to_sync(self.uid1)
|
|
found2 = badge_sync.find_user_to_sync(self.uid2)
|
|
self.assertEqual(self.sync_user1, found1)
|
|
self.assertEqual(self.sync_user2, found2)
|
|
|
|
# Badges of both not expired yet.
|
|
with self.app.app_context():
|
|
self._update_badge_expiry(2, 3)
|
|
found1 = badge_sync.find_user_to_sync(self.uid1)
|
|
found2 = badge_sync.find_user_to_sync(self.uid2)
|
|
self.assertEqual(self.sync_user1, found1)
|
|
self.assertEqual(self.sync_user2, found2)
|
|
|
|
|
|
class FetchHTMLTest(AbstractSyncTest):
|
|
@httpmock.activate
|
|
def test_happy(self):
|
|
from pillar import badge_sync
|
|
|
|
def check_request(request: requests.PreparedRequest):
|
|
if request.headers['Authorization'] != 'Bearer find-this-token-uid1':
|
|
return 403, {}, 'BAD TOKEN'
|
|
return 200, {'Content-Type': 'text/html; charset=utf-8'}, 'твоја мајка'.encode()
|
|
|
|
httpmock.add_callback('GET', 'http://id.local:8001/api/badges/1947/html/s', check_request)
|
|
|
|
with self.app.app_context():
|
|
badge_html = badge_sync.fetch_badge_html(requests.Session(), self.sync_user1, 's')
|
|
self.assertEqual('твоја мајка', badge_html)
|
|
|
|
@httpmock.activate
|
|
def test_internal_server_error(self):
|
|
from pillar import badge_sync
|
|
|
|
httpmock.add('GET', 'http://id.local:8001/api/badges/1947/html/s',
|
|
body='oops', status=500)
|
|
|
|
with self.assertRaises(badge_sync.StopRefreshing), self.app.app_context():
|
|
badge_sync.fetch_badge_html(requests.Session(), self.sync_user1, 's')
|
|
|
|
@httpmock.activate
|
|
def test_no_badge(self):
|
|
from pillar import badge_sync
|
|
|
|
httpmock.add('GET', 'http://id.local:8001/api/badges/1947/html/s',
|
|
body='', status=204)
|
|
with self.app.app_context():
|
|
badge_html = badge_sync.fetch_badge_html(requests.Session(), self.sync_user1, 's')
|
|
self.assertEqual('', badge_html)
|
|
|
|
@httpmock.activate
|
|
def test_no_such_user(self):
|
|
from pillar import badge_sync
|
|
|
|
httpmock.add('GET', 'http://id.local:8001/api/badges/1947/html/s',
|
|
body='Not Found', status=404)
|
|
with self.app.app_context():
|
|
badge_html = badge_sync.fetch_badge_html(requests.Session(), self.sync_user1, 's')
|
|
self.assertEqual('', badge_html)
|
|
|
|
@httpmock.activate
|
|
def test_no_connection_possible(self):
|
|
from pillar import badge_sync
|
|
|
|
with self.assertRaises(badge_sync.StopRefreshing), self.app.app_context():
|
|
badge_sync.fetch_badge_html(requests.Session(), self.sync_user1, 's')
|
|
|
|
|
|
class RefreshAllTest(AbstractSyncTest):
|
|
@httpmock.activate
|
|
def test_happy(self):
|
|
from pillar import badge_sync
|
|
|
|
httpmock.add('GET', 'http://id.local:8001/api/badges/1947/html/s',
|
|
body='badges for Agent 47')
|
|
httpmock.add('GET', 'http://id.local:8001/api/badges/4488/html/s',
|
|
body='badges for that other user')
|
|
|
|
with self.app.app_context():
|
|
badge_sync.refresh_all_badges(timelimit=datetime.timedelta(seconds=4))
|
|
|
|
db_user1 = self.get('/api/users/me', auth_token=self.sync_user1.token).json
|
|
db_user2 = self.get('/api/users/me', auth_token=self.sync_user2.token).json
|
|
self.assertEqual('badges for Agent 47', db_user1['badges']['html'])
|
|
self.assertEqual('badges for that other user', db_user2['badges']['html'])
|
|
|
|
@httpmock.activate
|
|
def test_timelimit(self):
|
|
from pillar import badge_sync
|
|
|
|
# This shouldn't hit any connection error, because it should immediately
|
|
# hit the time limit, before doing any call to Blender ID.
|
|
with self.app.app_context():
|
|
badge_sync.refresh_all_badges(timelimit=datetime.timedelta(seconds=-4))
|
|
|
|
@httpmock.activate
|
|
def test_remove_badges(self):
|
|
from pillar import badge_sync
|
|
from pillar.api.utils import utcnow
|
|
|
|
# Make sure the user has a badge before getting the 204 No Content from Blender ID.
|
|
self._update_badge_expiry(-10, 10)
|
|
|
|
httpmock.add('GET', 'http://id.local:8001/api/badges/1947/html/s',
|
|
body='', status=204)
|
|
|
|
with self.app.app_context():
|
|
badge_sync.refresh_all_badges(
|
|
only_user_id=self.uid1,
|
|
timelimit=datetime.timedelta(seconds=4))
|
|
expected_expire = utcnow() + self.app.config['BLENDER_ID_BADGE_EXPIRY']
|
|
|
|
# Get directly from MongoDB because JSON doesn't support datetimes.
|
|
users_coll = self.app.db('users')
|
|
db_user1 = users_coll.find_one(self.uid1)
|
|
|
|
self.assertEqual('', db_user1['badges']['html'])
|
|
margin = datetime.timedelta(minutes=1)
|
|
self.assertLess(expected_expire - margin, db_user1['badges']['expires'])
|
|
self.assertGreater(expected_expire + margin, db_user1['badges']['expires'])
|
|
|
|
|
|
class RefreshSingleTest(AbstractSyncTest):
|
|
@httpmock.activate
|
|
def test_happy(self):
|
|
from pillar import badge_sync
|
|
|
|
httpmock.add('GET', 'http://id.local:8001/api/badges/1947/html/s',
|
|
body='badges for Agent 47')
|
|
|
|
db_user1 = self.get('/api/users/me', auth_token=self.sync_user1.token).json
|
|
self.assertNotIn('badges', db_user1)
|
|
|
|
with self.app.app_context():
|
|
badge_sync.refresh_single_user(self.uid1)
|
|
|
|
db_user1 = self.get('/api/users/me', auth_token=self.sync_user1.token).json
|
|
self.assertEqual('badges for Agent 47', db_user1['badges']['html'])
|