def user_logged_out(shard, user_id): """Notifies other users that the given user has logged out of a shard.""" def txn(): login_record = models.LoginRecord.get_by_id(user_id) if not login_record: raise ndb.Rollback() if not login_record.online: raise ndb.Rollback() login_record.online = False login_record.put() return login_record login_record = ndb.transaction(txn) if not login_record: logging.warning('Tried to log out user_id=%r from shard=%r, ' 'but LoginRecord did not exist', user_id, shard) return posts.insert_post( shard, archive_type=models.Post.USER_LOGOUT, nickname=login_record.nickname, user_id=user_id, body='%s has left' % login_record.nickname) invalidate_user_cache(shard) logging.debug('Logged out user_id=%r from shard=%r', user_id, shard)
def testReceiptExists(self): """Tests that post receipts prevent duplicate PostReferences.""" shard = models.Shard(id='my-shard-name') shard.put() already_posted_key = posts.insert_post( shard.shard_id, post_id='my-id-1234', archive_type=models.Post.CHAT, nickname='My name', user_id='abc', body='Here is my message') models.Receipt(id=shard.shard_id, parent=already_posted_key).put() new_post_key = posts.insert_post( shard.shard_id, post_id='my-id-7890', archive_type=models.Post.CHAT, nickname='My name', user_id='abc', body='My second message') posts.apply_posts(shard.shard_id) ref_list = list(models.PostReference.query()) self.assertEquals(1, len(ref_list)) found_ref = ref_list[0] self.assertEquals(new_post_key.id(), found_ref.post_id)
def testReplicate(self): """Tests replicating a post to a topic shard.""" shard = models.Shard(id='my-shard-name') shard.put() # This post was before the topic change and won't be replicated first_post = posts.insert_post( shard.shard_id, archive_type=models.Post.CHAT, nickname='My name', user_id='abc', body='This will not be replicated') posts.apply_posts(shard.shard_id) topic_shard_id, change_topic_post = topics.start_topic( shard.shard_id, 'my-user-id', 'my-post-id', 'my name', 'topic title', 'topic description') after_shard = shard.key.get() self.assertEquals(None, after_shard.current_topic) posts.apply_posts(shard.shard_id) after_shard = shard.key.get() self.assertEquals(topic_shard_id, after_shard.current_topic) # The post that caused the topic change will be replicated shard_ref_list = list(models.PostReference.query(ancestor=shard.key)) shard_post_ids = [r.post_id for r in shard_ref_list] self.assertEquals([first_post.id(), change_topic_post.id()], shard_post_ids) topic_shard = models.Shard.get_by_id(topic_shard_id) self.assertEquals( None, models.PostReference.query(ancestor=topic_shard.key).get()) posts.apply_posts(topic_shard_id) topic_ref_list = list(models.PostReference.query( ancestor=topic_shard.key)) topic_post_ids = [r.post_id for r in topic_ref_list] self.assertEquals([change_topic_post.id()], topic_post_ids) # This post is after the topic change and will be replicated replicated_post = post_key = posts.insert_post( shard.shard_id, archive_type=models.Post.CHAT, nickname='My name', user_id='abc', body='Here is my message') posts.apply_posts(shard.shard_id) posts.apply_posts(topic_shard_id) topic_ref_list = list(models.PostReference.query( ancestor=topic_shard.key)) topic_post_ids = [r.post_id for r in topic_ref_list] self.assertEquals([change_topic_post.id(), replicated_post.id()], topic_post_ids)
def testMultiple(self): """Tests inserting multiple posts and applying them together.""" shard = models.Shard(id='my-shard-name') shard.put() post_key_list = [] for i in xrange(5): post_key_list.append(posts.insert_post( shard.shard_id, post_id='my-id-%d' % i, archive_type=models.Post.CHAT, nickname='My name', user_id='abc', body='Here is my message %d' % i)) self.assertEquals(5, models.Post.query().count()) self.assertEquals(None, models.PostReference.query().get()) posts.apply_posts(shard.shard_id) ref_list = list(models.PostReference.query()) ref_ids = [r.key.id() for r in ref_list] self.assertEquals([1, 2, 3, 4, 5], ref_ids) receipt_list = list(models.Receipt.query()) receipt_parents = [r.key.parent() for r in receipt_list] self.assertEquals(post_key_list, receipt_parents) shard_after = shard.key.get() self.assertEquals(6, shard_after.sequence_number)
def testSingle(self): """Tests successfully inserting and applying a single Post.""" shard = models.Shard(id='my-shard-name') shard.put() post_key = posts.insert_post( shard.shard_id, post_id='my-id-1234', archive_type=models.Post.CHAT, nickname='My name', user_id='abc', body='Here is my message') found_post = models.Post.query().get() self.assertEquals(post_key, found_post.key) self.assertEquals(None, models.PostReference.query().get()) self.assertEquals(None, models.Receipt.query().get()) posts.apply_posts(shard.shard_id) found_ref = models.PostReference.query().get() self.assertEquals(1, found_ref.key.id()) self.assertEquals(found_post.post_id, found_ref.post_id) found_receipt = models.Receipt.query().get() self.assertEquals(post_key, found_receipt.key.parent()) shard_after = shard.key.get() self.assertEquals(2, shard_after.sequence_number)
def make_post(self, post_id, message, shard_id=None): """Makes a test post.""" if shard_id is None: shard_id = self.shard.shard_id post_key = posts.insert_post( shard_id, post_id=post_id, archive_type=models.Post.CHAT, nickname='My name', user_id=self.user_id, body=message) posts.apply_posts(shard_id) return post_key
def start_topic(root_shard_id, user_id, post_id, nickname, title, description): """Starts a new topic under a root shard.""" shard = models.Shard( id=models.human_uuid(), title=title, description=description, creation_nickname=nickname, root_shard=root_shard_id) shard.put() post_key = posts.insert_post( root_shard_id, post_id=post_id, archive_type=models.Post.TOPIC_START, nickname=nickname, user_id=user_id, title=title, body=description, new_topic=shard.shard_id) return shard.shard_id, post_key
def testChannelMessage(self): """Tests that post and apply both send message updates.""" shard = models.Shard(id='my-shard-name') shard.put() channel_stub = self.testbed.get_stub(testbed.CHANNEL_SERVICE_NAME) user_id = 'my-user-id' presence.user_logged_in(shard.shard_id, user_id) _, browser_token = presence.change_presence( shard.shard_id, user_id, 'name here', True, True, False) channel_stub.connect_channel(browser_token) # This clears the presence Post from change_presence() posts.apply_posts(shard.shard_id) channel_stub.pop_first_message(browser_token) post_key = posts.insert_post( shard.shard_id, archive_type=models.Post.CHAT, nickname='My name', user_id='abc', body='Here is my message') message = channel_stub.pop_first_message(browser_token) found_posts = json.loads(message)['posts'] post = post_key.get() expected_posts = posts.marshal_posts(shard.shard_id, [post]) self.assertEquals(expected_posts, found_posts) self.assertEquals(None, expected_posts[0]['sequenceId']) posts.apply_posts(shard.shard_id) post = post_key.get() message = channel_stub.pop_first_message(browser_token) found_posts = json.loads(message)['posts'] self.assertEquals(2, found_posts[0]['sequenceId']) post = post_key.get() post.sequence = 2 # Pretend to do what apply_posts does expected_posts = posts.marshal_posts(shard.shard_id, [post]) self.assertEquals(expected_posts, found_posts)
def change_presence(shard, user_id, nickname, accepted_terms, sounds_enabled, retrying, email_address): """Changes the presence for a user.""" def txn(): last_nickname = None user_connected = True login = models.LoginRecord.get_by_id(user_id) if not login: login = models.LoginRecord( key=ndb.Key(models.LoginRecord._get_kind(), user_id), shard_id=shard) elif only_active_users(login): # This is a heartbeat presence check user_connected = False if maybe_update_token(login, force=retrying): logging.debug( 'Issuing channel token: user_id=%r, shard=%r, force=%r', user_id, shard, retrying) if nickname: # This is a potential nickname change. Right now the client # always sends the nickname on every request, so we need to # check for the difference to detect a rename. last_nickname = login.nickname login.nickname = nickname if accepted_terms: # This is a ToS acceptance login.accepted_terms_version = config.terms_version login.online = True login.sounds_enabled = sounds_enabled login.email_address = email_address or None login.put() return last_nickname, user_connected, login.browser_token last_nickname, user_connected, browser_token = ndb.transaction(txn) # Invalidate the cache so the nickname will be updated next time # someone requests the roster. invalidate_user_cache(shard) message = None archive_type = None if nickname and last_nickname and last_nickname != nickname: message = '%s has changed their nickname to %s' % ( last_nickname, nickname) archive_type = models.Post.USER_UPDATE logging.debug('User update user_id=%r, shard=%r', user_id, shard) elif user_connected: message = '%s has joined' % nickname archive_type = models.Post.USER_LOGIN logging.debug('User joined: user_id=%r, shard=%r', user_id, shard) else: logging.debug('User heartbeat: user_id=%r to shard=%r', user_id, shard) if archive_type: posts.insert_post( shard, archive_type=archive_type, nickname=nickname, user_id=user_id, body=message) else: # As long as users are heart-beating, we should be running a # cleanup task for this shard. enqueue_cleanup_task(shard) return user_connected, browser_token