def test_get_feed(self): """ Attempt to get a users feed under certain circumstances. """ # Get test user user1 = create_account('user1', '*****@*****.**', 'Password') # Ensure an empty feed is returned. Remember these are paginations self.assertEqual(len(get_feed(user1).items), 0) # Ensure a users own post is added to thier feed post1 = create_post(user1, 'user1', 'Test post') # Ensure the list is the correct length self.assertEqual(len(get_feed(user1).items), 1) self.assertEqual(get_feed(user1).total, 1) # Ensure the item is in Redis self.assertIn(post1, r.zrange(K.USER_FEED.format(user1), 0, -1)) # Create a second user, make 1 follow them, make then post and ensure # that the new users post appears in user 1s feed user2 = create_account('user2', '*****@*****.**', 'Password') follow_user(user1, user2) post2 = create_post(user2, 'user2', 'Test post') # Check user 1's feed for the next item self.assertEqual(len(get_feed(user1).items), 2) self.assertEqual(get_feed(user1).total, 2) # Ensure the item is in Redis self.assertIn(post2, r.zrange(K.USER_FEED.format(user1), 0, -1)) # Delete user 2 and ensure user 1's feed cleans itself delete_account(user2) self.assertEqual(len(get_feed(user1).items), 1) self.assertEqual(get_feed(user1).total, 1) # Ensure the item is not in Redis self.assertNotIn(post2, r.zrange(K.USER_FEED.format(user1), 0, -1))
def delete_account(user_id): """Will delete a users account. This **REMOVES ALL** details, posts, replies, etc. Not votes though. .. note: Ensure the user has authenticated this request. This is going to be the most *expensive* task in Pjuu, be warned. :param user_id: The `user_id` of the user to delete :type user_id: str """ # Delete the user from MongoDB m.db.users.remove({"_id": user_id}) # Remove all posts a user has ever made. This includes all votes # on the posts and all comments of the posts. # This calls the backend function from posts to do the deed posts_cursor = m.db.posts.find({"user_id": user_id}, {}) for post in posts_cursor: delete_post(post.get("_id")) # Remove all the following relationships from Redis # Delete all references to followers of the user. # This will remove the user from the other users following list # TODO Replace with ZSCAN follower_cursor = r.zrange(k.USER_FOLLOWERS.format(user_id), 0, -1) for follower_id in follower_cursor: # Clear the followers following list of the uid r.zrem(k.USER_FOLLOWING.format(follower_id), user_id) # Delete the followers list r.delete(k.USER_FOLLOWERS.format(user_id)) # Delete all references to the users the user is following # This will remove the user from the others users followers list # TODO Replace with ZSCAN followee_cursor = r.zrange(k.USER_FOLLOWING.format(user_id), 0, -1) for followee_id in followee_cursor: # Clear the followers list of people uid is following r.zrem(k.USER_FOLLOWERS.format(followee_id), user_id) # Delete the following list r.delete(k.USER_FOLLOWING.format(user_id)) # Delete the users feed, this may have been added too during this process. # Probably not but let's be on the safe side r.delete(k.USER_FEED.format(user_id)) # Delete the users alert list # DO NOT DELETE ANY ALERTS AS THESE ARE GENERIC r.delete(k.USER_ALERTS.format(user_id))
def test_delete_account_followers_following(self): """Does the users social graph go on deletion of account? """ user1 = create_account('user1', '*****@*****.**', 'Password') user2 = create_account('user2', '*****@*****.**', 'Password') # Friends :) self.assertTrue(follow_user(user1, user2)) self.assertTrue(follow_user(user2, user1)) # Ensure Redis's sorted sets are correct self.assertIn(user2, r.zrange(K.USER_FOLLOWERS.format(user1), 0, -1)) self.assertIn(user2, r.zrange(K.USER_FOLLOWING.format(user1), 0, -1)) self.assertIn(user1, r.zrange(K.USER_FOLLOWERS.format(user2), 0, -1)) self.assertIn(user1, r.zrange(K.USER_FOLLOWING.format(user2), 0, -1)) delete_account(user1) # Ensure sorted sets are emptied self.assertNotIn(user2, r.zrange(K.USER_FOLLOWERS.format(user1), 0, -1)) self.assertNotIn(user2, r.zrange(K.USER_FOLLOWING.format(user1), 0, -1)) self.assertNotIn(user1, r.zrange(K.USER_FOLLOWERS.format(user2), 0, -1)) self.assertNotIn(user1, r.zrange(K.USER_FOLLOWING.format(user2), 0, -1))
def test_delete_account_followers_following(self): """ Test delete_account() Followers & Following: Ensures that all data related to followers is removed during account deletion Note: This is not a full test of the users system. See users/test.py """ # Create test users user1 = create_user('user1', '*****@*****.**', 'Password') user2 = create_user('user2', '*****@*****.**', 'Password') # Make users follow each other self.assertTrue(follow_user(user1, user2)) self.assertTrue(follow_user(user2, user1)) # Ensure the uid's are in the relevant sorted sets self.assertIn(user2, r.zrange(K.USER_FOLLOWERS.format(user1), 0, -1)) self.assertIn(user2, r.zrange(K.USER_FOLLOWING.format(user1), 0, -1)) self.assertIn(user1, r.zrange(K.USER_FOLLOWERS.format(user2), 0, -1)) self.assertIn(user1, r.zrange(K.USER_FOLLOWING.format(user2), 0, -1)) # Delete test account 1 delete_account(user1) # Ensure the lists are empty self.assertNotIn(user2, r.zrange(K.USER_FOLLOWERS.format(user1), 0, -1)) self.assertNotIn(user2, r.zrange(K.USER_FOLLOWING.format(user1), 0, -1)) self.assertNotIn(user1, r.zrange(K.USER_FOLLOWERS.format(user2), 0, -1)) self.assertNotIn(user1, r.zrange(K.USER_FOLLOWING.format(user2), 0, -1))
def test_create_post(self): """Tests creating and getting a post """ # Create a user to test creating post user1 = create_account('user1', '*****@*****.**', 'Password') # Create post post1 = create_post(user1, 'user1', 'Test post') # Check the post actually exists self.assertIsNotNone(post1) # Check that all the hash members are what we expect post = get_post(post1) self.assertIsNotNone(post) self.assertEqual(post.get('_id'), post1) self.assertEqual(post.get('user_id'), user1) self.assertEqual(post.get('body'), 'Test post') self.assertEqual(post.get('score'), 0) self.assertEqual(post.get('comment_count'), 0) # Check the memebers we don't know the answer to self.assertIsNotNone(post.get('created')) self.assertNotIn('upload', post) # Ensure this post is the users feed (populate_feed) self.assertIn(post1, r.zrange(K.USER_FEED.format(user1), 0, -1)) # Testing getting post with invalid arguments # Test getting a post that does not exist self.assertIsNone(get_post(K.NIL_VALUE)) # Create a post with an image image = io.BytesIO(open('tests/upload_test_files/otter.jpg').read()) post2 = create_post(user1, 'user1', 'Test post #2', upload=image) self.assertIsNotNone(post2) post = get_post(post2) self.assertIn('upload', post) self.assertIsNotNone(post.get('upload')) image = io.BytesIO(open('tests/upload_test_files/otter.gif').read()) post3 = create_post(user1, 'user1', 'Test post #2', upload=image) self.assertIsNotNone(post3) post = get_post(post3) self.assertIn('upload', post) self.assertIn('upload_animated', post) self.assertIsNotNone(post.get('upload')) self.assertIsNotNone(post.get('upload_animated')) # Create a post with a broken image, ensure it's handled correctly image = io.BytesIO() post4 = create_post(user1, 'user1', 'Test post #3', upload=image) self.assertIsNone(post4) post4 = get_post(post4) self.assertIsNone(post4)
def populate_approved_followers_feeds(user_id, post_id, timestamp): """Fan out a post_id to all the users approved followers.""" # Get a list of ALL users who are following a user followers = r.zrange(k.USER_APPROVED.format(user_id), 0, -1) # This is not transactional as to not hold Redis up. for follower_id in followers: # Add the pid to the list r.zadd(k.USER_FEED.format(follower_id), timestamp, post_id) # Stop followers feeds from growing to large, doesn't matter if it # doesn't exist r.zremrangebyrank(k.USER_FEED.format(follower_id), 0, -1000)
def populate_feeds(uid, pid): """Fan out a pid to all the users with uid's followers """ # Get a list of ALL users who are following a user followers = r.zrange(K.USER_FOLLOWERS.format(uid), 0, -1) # This is not transactional as to not hold Redis up. for fid in followers: # Add the pid to the list r.lpush(K.USER_FEED.format(fid), pid) # Stop followers feeds from growing to large, doesn't matter if it # doesn't exist r.ltrim(K.USER_FEED.format(fid), 0, 999)
def test_alertmanager(self): """ Test the alert manager. Similar to the above a very simple test. Will check that it can alert users and one can be created. """ # Create our alert manager am = AlertManager() # Try and load a non-existant alert self.assertIsNone(am.get(k.NIL_VALUE)) # Try and alert on something which is not an alert self.assertRaises(ValueError, lambda: am.alert('ALERT', k.NIL_VALUE)) # Test that alerting a single users does not work, they need to be # iterable # Create an alert alert = BaseAlert(k.NIL_VALUE) self.assertRaises(TypeError, lambda: am.alert(alert, 1)) # Create a couple of users user1 = create_account('user1', '*****@*****.**', 'Password') user2 = create_account('user2', '*****@*****.**', 'Password') # Ensure the length of user1's alert feed is 0 self.assertEqual(r.zcard(k.USER_ALERTS.format(user1)), 0) # Create an alert from user2 alert = BaseAlert(user2) # Alert user1 am.alert(alert, [user1]) # Ensure the length of user1's alert feed is 1 self.assertEqual(r.zcard(k.USER_ALERTS.format(user1)), 1) # Get alerts for user1, user Redis directly alerts = r.zrange(k.USER_ALERTS.format(user1), 0, 0) # Get the alert from alerts alert = am.get(alerts[0]) self.assertIsNotNone(alert) self.assertEqual(alert.user.get('username'), 'user2') self.assertEqual(alert.user.get('email'), '*****@*****.**') # Delete test2 and ensure getting the alert returns None delete_account(user2) alert = am.get(alerts[0]) self.assertIsNone(alert)
def test_create_post(self): """Tests creating and getting a post """ # Create a user to test creating post user1 = create_account('user1', '*****@*****.**', 'Password') # Create post post1 = create_post(user1, 'user1', 'Test post') # Check the post actually exists self.assertIsNotNone(post1) # Check that all the hash members are what we expect post = get_post(post1) self.assertIsNotNone(post) self.assertEqual(post.get('_id'), post1) self.assertEqual(post.get('user_id'), user1) self.assertEqual(post.get('body'), 'Test post') self.assertEqual(post.get('score'), 0) self.assertEqual(post.get('comment_count'), 0) # Check the memebers we don't know the answer to self.assertIsNotNone(post.get('created')) self.assertNotIn('upload', post) # Ensure this post is the users feed (populate_feed) self.assertIn(post1, r.zrange(K.USER_FEED.format(user1), 0, -1)) # Testing getting post with invalid arguments # Test getting a post that does not exist self.assertIsNone(get_post(K.NIL_VALUE)) # Create a post with an image image = io.BytesIO(open('tests/upload_test_files/otter.jpg').read()) post2 = create_post(user1, 'user1', 'Test post #2', upload=image) self.assertIsNotNone(post2) post = get_post(post2) self.assertIn('upload', post) self.assertIsNotNone(post.get('upload')) # Create a post with a broken image, ensure it's handled correctly image = io.BytesIO() post3 = create_post(user1, 'user1', 'Test post #3', upload=image) self.assertIsNone(post3) post3 = get_post(post3) self.assertIsNone(post3)
def get_subscribers(post_id): """Return a list of subscribers 'user_id's for a given post """ return r.zrange(k.POST_SUBSCRIBERS.format(post_id), 0, -1)
def test_follow_unfollow_get_followers_following_is_following(self): """ Test everything about following. There is not that much to it to deserve 3 seperate methods. """ # Create two test users user1 = create_account('user1', '*****@*****.**', 'Password') user2 = create_account('user2', '*****@*****.**', 'Password') # Ensure is_following() is false atm self.assertFalse(is_following(user1, user2)) self.assertFalse(is_following(user2, user1)) # Ensure user 1 can follow user 2 self.assertTrue(follow_user(user1, user2)) # Ensure the user can't follow them again self.assertFalse(follow_user(user1, user2)) # And visa-versa self.assertTrue(follow_user(user2, user1)) # Ensre the user can't follow them again self.assertFalse(follow_user(user2, user1)) # Ensure the id's are in the Redis sorted sets, followers and following self.assertIn(user2, r.zrange(k.USER_FOLLOWING.format(user1), 0, -1)) self.assertIn(user2, r.zrange(k.USER_FOLLOWERS.format(user1), 0, -1)) self.assertIn(user1, r.zrange(k.USER_FOLLOWING.format(user2), 0, -1)) self.assertIn(user1, r.zrange(k.USER_FOLLOWERS.format(user2), 0, -1)) # Ensure the get_followers and get_following functions return # the correct data self.assertEqual(len(get_following(user1).items), 1) self.assertEqual(len(get_following(user1).items), 1) self.assertEqual(len(get_following(user2).items), 1) self.assertEqual(len(get_following(user2).items), 1) # Ensure the totals are correct self.assertEqual(get_following(user1).total, 1) self.assertEqual(get_followers(user1).total, 1) self.assertEqual(get_following(user2).total, 1) self.assertEqual(get_followers(user2).total, 1) # Make sure is_following() returns correctly self.assertTrue(is_following(user1, user2)) self.assertTrue(is_following(user2, user1)) # User 1 unfollow user 2 and ensure the sorted sets are updated self.assertTrue(unfollow_user(user1, user2)) self.assertNotIn(user2, r.zrange(k.USER_FOLLOWING.format(user1), 0, -1)) self.assertNotIn(user1, r.zrange(k.USER_FOLLOWERS.format(user2), 0, -1)) # Ensure the user can't unfollow the user again self.assertFalse(unfollow_user(user1, user2)) # Ensure is_following shows correctly self.assertFalse(is_following(user1, user2)) # Test what happens when we delete an account. # Ensure user 2 is still following user1 self.assertTrue(is_following(user2, user1)) # This should clean delete_account(user1) # Ensure this has cleaned user2 following list self.assertFalse(is_following(user2, user1)) # Ensure get_followers and get_following return the correct value for # user2 self.assertEqual(len(get_following(user2).items), 0) # Ensure the totals are correct self.assertEqual(get_following(user2).total, 0) self.assertEqual(get_followers(user2).total, 0) # Make sure is_following() returns correctly self.assertFalse(is_following(user1, user2)) self.assertFalse(is_following(user2, user1)) # I don't want to play with the above testing to much. I am adding # in a test for self cleaning lists. I am going to reset this test # case by manually flushing the Redis database :) r.flushdb() # Back to normal, I don't like artificially uping the number of tests. # Test the self cleaning lists in case there is an issue with Redis # during an account deletion. We need 2 new users. user1 = create_account('user1', '*****@*****.**', 'Password') user2 = create_account('user2', '*****@*****.**', 'Password') # Follow each other. self.assertTrue(follow_user(user1, user2)) self.assertTrue(follow_user(user2, user1)) # Manually delete user1 m.db.users.remove({'_id': user1}) # Ensure user1 appears in both user2's followers and following lists self.assertIn(user1, r.zrange(k.USER_FOLLOWERS.format(user2), 0, -1)) self.assertIn(user1, r.zrange(k.USER_FOLLOWING.format(user2), 0, -1)) # Ensure if we actuallt get the lists from the backend functions user1 # is not there self.assertEqual(get_followers(user2).total, 0) self.assertEqual(get_following(user2).total, 0)
def delete_account(user_id): """Will delete a users account. This **REMOVES ALL** details, posts, replies, etc. Not votes though. .. note: Ensure the user has authenticated this request. This is going to be the most *expensive* task in Pjuu, be warned. :param user_id: The `user_id` of the user to delete :type user_id: str """ # Get the user object we will need this to remove the avatar user = get_user(user_id) # Delete the user from MongoDB m.db.users.remove({'_id': user_id}) # If the user has an avatar remove it if user.get('avatar'): delete_upload(user.get('avatar')) # Remove all posts a user has ever made. This includes all votes # on the posts and all comments of the posts. # This calls the backend function from posts to do the deed posts_cursor = m.db.posts.find({'user_id': user_id}, {}) for post in posts_cursor: delete_post(post.get('_id')) # Remove all the following relationships from Redis # Delete all references to followers of the user. # This will remove the user from the other users following list # TODO Replace with ZSCAN follower_cursor = r.zrange(k.USER_FOLLOWERS.format(user_id), 0, -1) for follower_id in follower_cursor: # Clear the followers following list of the uid r.zrem(k.USER_FOLLOWING.format(follower_id), user_id) # Delete the followers list r.delete(k.USER_FOLLOWERS.format(user_id)) # Delete all references to the users the user is following # This will remove the user from the others users followers list # TODO Replace with ZSCAN followee_cursor = r.zrange(k.USER_FOLLOWING.format(user_id), 0, -1) for followee_id in followee_cursor: # Clear the followers list of people uid is following r.zrem(k.USER_FOLLOWERS.format(followee_id), user_id) # Delete the following list r.delete(k.USER_FOLLOWING.format(user_id)) # Delete the users feed, this may have been added too during this process. # Probably not but let's be on the safe side r.delete(k.USER_FEED.format(user_id)) # Delete the users alert list # DO NOT DELETE ANY ALERTS AS THESE ARE GENERIC r.delete(k.USER_ALERTS.format(user_id))
def delete_account(uid): """Will delete a users account. This _MUST_ _REMOVE_ _ALL_ details, comments, posts, etc. Note: Ensure the user has authenticated this request. This is going to be the most _expensive_ task in Pjuu, be warned. """ # Get some information from the hashes to delete lookup keys username = r.hget(K.USER.format(uid), 'username') email = r.hget(K.USER.format(uid), 'email') # Clear the users lookup keys and user account. These are not needed pipe = r.pipeline() # Delete lookup keys. This will stop the user being found or logging in pipe.set(K.UID_USERNAME.format(username), K.NIL_VALUE) pipe.expire(K.UID_USERNAME.format(username), K.EXPIRE_SECONDS) pipe.set(K.UID_EMAIL.format(email), K.NIL_VALUE) pipe.expire(K.UID_EMAIL.format(email), K.EXPIRE_SECONDS) # Delete user account pipe.delete(K.USER.format(uid)) pipe.execute() # Remove all posts a user has ever made. This includes all votes # on that post and all comments. pids = r.lrange(K.USER_POSTS.format(uid), 0, -1) for pid in pids: # Delete post r.delete(K.POST.format(pid)) # Delete all the votes made on the post r.delete(K.POST_VOTES.format(pid)) # Delete posts subscribers list r.delete(K.POST_SUBSCRIBERS.format(pid)) cids = r.lrange(K.POST_COMMENTS.format(pid), 0, -1) for cid in cids: # Get author, ensure uid is an int cid_author = r.hget(K.COMMENT.format(cid), 'uid') # Delete comment r.delete(K.COMMENT.format(cid)) # Delete comment votes r.delete(K.COMMENT_VOTES.format(cid)) # Remove the cid from users comment list # This may remove some of ours. This will just make deleting # a bit quicker r.lrem(K.USER_COMMENTS.format(cid_author), 0, cid) # Delete the comments list r.delete(K.POST_COMMENTS.format(pid)) # Delete the users post list r.delete(K.USER_POSTS.format(uid)) # Delete all comments the user has every made. Including all votes on # those comments # This is a stripped down version of above for post comments. # We are not going to clean the lists related to the posts, they will # self clean. We also do not need to clear the comments from the users # comments list as it will be getting deleted straight after cids = r.lrange(K.USER_COMMENTS.format(uid), 0, -1) for cid in cids: # Get author, ensure uid is an int cid_author = r.hget(K.COMMENT.format(cid), 'uid') # Delete comment r.delete(K.COMMENT.format(cid)) # Delete comment votes r.delete(K.COMMENT_VOTES.format(cid)) # Delete the comments list r.delete(K.USER_COMMENTS.format(uid)) # Delete all references to followers of the the user. # This will remove the user from the other users following list fids = r.zrange(K.USER_FOLLOWERS.format(uid), 0, -1) for fid in fids: # Clear the followers following list of the uid r.zrem(K.USER_FOLLOWING.format(fid), uid) # Delete the followers list r.delete(K.USER_FOLLOWERS.format(uid)) # Delete all references to the users the user is following # This will remove the user from the others users followers list fids = r.zrange(K.USER_FOLLOWING.format(uid), 0, -1) for fid in fids: # Clear the followers list of people uid is following r.zrem(K.USER_FOLLOWERS.format(fid), uid) # Delete the following list r.delete(K.USER_FOLLOWING.format(uid)) # Finally delete the users feed, this may have been added too during this # process. Probably not but let's be on the safe side r.delete(K.USER_FEED.format(uid)) # Delete the users alert list # DO NOT DELETE ANY ALERTS AS THESE ARE GENERIC r.delete(K.USER_ALERTS.format(uid))