Beispiel #1
0
 def _CreateBadOperation(self, user_id, device_id, callback):
     """Creates a photo share for a photo which doesn't exist."""
     request = {
         'user_id':
         user_id,
         'activity': {
             'activity_id': 'a123',
             'timestamp': time.time()
         },
         'viewpoint': {
             'viewpoint_id': Viewpoint.ConstructViewpointId(100, 100),
             'type': Viewpoint.EVENT
         },
         'episodes': [{
             'existing_episode_id': 'eg8QVrk3S',
             'new_episode_id': 'eg8QVrk3T',
             'timestamp': time.time(),
             'photo_ids': ['pg8QVrk3S']
         }],
         'contacts': [{
             'identity': 'Local:testing1',
             'name': 'Peter Mattis'
         }]
     }
     Operation.CreateAndExecute(self._client, user_id, device_id,
                                'ShareNewOperation.Execute', request,
                                callback)
Beispiel #2
0
  def testInvalidViewpointMetadata(self):
    """Verifies detection of invalid viewpoint metadata."""
    viewpoint = self._CreateTestViewpoint('vp1', self._user.user_id, [])
    self._RunAsync(Activity.CreateShareNew, self._client, self._user.user_id, 'vp1', 'a1',
                   time.time() - 10, 0, [{'new_episode_id': 'ep1', 'photo_ids': []}], [])

    # Bypass checks in DBObject and force last_updated and timestamp to be updated to None.
    viewpoint = Viewpoint.CreateFromKeywords(viewpoint_id='vp1')
    viewpoint._columns['last_updated'].SetModified(True)
    viewpoint._columns['timestamp'].SetModified(True)
    self._RunAsync(viewpoint.Update, self._client)

    self._RunAsync(self._checker.CheckAllViewpoints)

    corruption_text = \
      '  ---- viewpoint vp1 ----\n' \
      '  invalid viewpoint metadata (2 instances)\n' \
      '  missing followed (1 instance)\n' \
      '\n' \
      'python dbchk.py --devbox --repair=True --viewpoints=vp1'

    self.assertEqual(self._checker._email_args['text'],
                     'Found corruption(s) in database:\n\n%s' % corruption_text)

    self._RunDbChk({'viewpoints': ['vp1'], 'repair': True})

    # Validate by checking again and finding no issues.
    self._RunAsync(self._checker.CheckAllViewpoints)
    self.assertIsNone(self._checker._email_args)
Beispiel #3
0
 def _FormatAttribute(self, name, value):
     """Returns the attribute value; If none, returns '-'. Formats by
 default the following fields: 'viewpoint_id', 'episode_id',
 'photo_id', 'timestamp', 'Location', 'Placemark'.
 """
     if name == 'viewpoint_id' or name == 'private_vp_id':
         did, (vid, sid) = Viewpoint.DeconstructViewpointId(value)
         pretty = '%s/%d/%d' % (value, did, vid)
         return FmtDefault._ViewpointLink(value, pretty)
     elif name == 'user_id' or name == 'sender_id':
         return self._UserLink(value)
     elif name == 'episode_id' or name == 'parent_ep_id':
         ts, did, (eid, sid) = Episode.DeconstructEpisodeId(value)
         pretty = '%s/%d/%d' % (value, did, eid)
         return self._EpisodeLink(value, pretty)
     elif name == 'photo_id' or name == 'parent_id':
         ts, did, (pid, sid) = Photo.DeconstructPhotoId(value)
         pretty = '%s/%d/%d' % (value, did, pid)
         return self._PhotoLink(value, pretty)
     elif name == 'timestamp' or name == 'last_updated' or name == 'expires' or name == 'last_fetch':
         return self._FormatTimestamp(value)
     elif name == 'location':
         return self._XEscape(', '.join([
             '%s: %s' % (k, v)
             for k, v in UnpackLocation(value)._asdict().items()
         ]))
     elif name == 'placemark':
         return self._XEscape(', '.join([
             '%s: %s' % (k, v)
             for k, v in UnpackPlacemark(value)._asdict().items()
         ]))
     else:
         return self._XEscape('%s' % value)
    def testRemoveNonExistentViewpointFails(self):
        """Error: Remove a non-existent viewpoint."""

        # Make up a viewpoint id for the test user's device.
        vp_id = Viewpoint.ConstructViewpointId(self._device_ids[0], 129)

        # Try to remove the viewpoint and expect failure.
        self.assertRaisesHttpError(403, self._tester.RemoveViewpoint,
                                   self._cookie, vp_id)
Beispiel #5
0
    def _QueryFollowers():
        """Produces list of (followers, last_key) tuples, one for each viewpoint in the request."""
        tasks = []
        for vp_id in viewpoint_ids:
            if get_followers:
                tasks.append(Viewpoint.QueryFollowers(client, vp_id))
            else:
                tasks.append(util.GenConstant(None))

        follower_results = yield tasks
        raise gen.Return(follower_results)
 def _CreateViewpointDict(self, user_cookie, **update_vp_dict):
   """Create dict() for a test viewpoint, overriding default values with
   whatever is passed in "update_vp_dict"."""
   user_id, device_id = self._tester.GetIdsFromCookie(user_cookie)
   vp_dict = {'viewpoint_id': Viewpoint.ConstructViewpointId(device_id, self._test_id),
              'title': 'Title %s' % self._test_id,
              'description': 'Description %s. 朋友你好.' % self._test_id,
              'name': 'Name %s' % self._test_id,
              'type': Viewpoint.EVENT}
   self._test_id += 1
   vp_dict.update(**update_vp_dict)
   return vp_dict
Beispiel #7
0
  def CreateProspective(cls, client, user_id, webapp_dev_id, identity_key, timestamp):
    """Creates a prospective user with the specified user id. web device id, and identity key.

    A prospective user is typically created when photos are shared with a contact that is not
    yet a Viewfinder user.

    Returns a tuple containing the user and identity.
    """
    from viewfinder.backend.db.viewpoint import Viewpoint

    identity_type, identity_value = Identity.SplitKey(identity_key)

    # Ensure that identity is created.
    identity = yield gen.Task(Identity.CreateProspective,
                              client,
                              identity_key,
                              user_id,
                              timestamp)

    # Create the default viewpoint.
    viewpoint = yield Viewpoint.CreateDefault(client, user_id, webapp_dev_id, timestamp)

    # By default, send alerts when a new conversation is started. Send email alerts if the
    # identity is email, or sms alerts if the identity is phone.
    email_alerts = AccountSettings.EMAIL_ON_SHARE_NEW if identity_type == 'Email' else AccountSettings.EMAIL_NONE
    sms_alerts = AccountSettings.SMS_ON_SHARE_NEW if identity_type == 'Phone' else AccountSettings.SMS_NONE
    settings = AccountSettings.CreateForUser(user_id,
                                             email_alerts=email_alerts,
                                             sms_alerts=sms_alerts,
                                             push_alerts=AccountSettings.PUSH_NONE)
    yield gen.Task(settings.Update, client)

    # Create a Friend relation (every user is friends with himself).
    friend = Friend.CreateFromKeywords(user_id=user_id, friend_id=user_id)
    yield gen.Task(friend.Update, client)

    # Create the prospective user.
    email = identity_value if identity_type == 'Email' else None
    phone = identity_value if identity_type == 'Phone' else None
    user = User.CreateFromKeywords(user_id=user_id,
                                   private_vp_id=viewpoint.viewpoint_id,
                                   webapp_dev_id=webapp_dev_id,
                                   email=email,
                                   phone=phone,
                                   asset_id_seq=User._RESERVED_ASSET_ID_COUNT,
                                   signing_key=secrets.CreateSigningKeyset('signing_key'))
    yield gen.Task(user.Update, client)

    raise gen.Return((user, identity))
    def _Update(self):
        """Updates the database:
       1. Revives any followers that have removed the viewpoint.
       2. Creates new episodes and posts.
       3. Creates cover photo, if needed.
    """
        # Revive any REMOVED followers.
        yield gen.Task(Follower.ReviveRemovedFollowers, self._client,
                       self._followers)

        # Create episode and posts that did not exist at the beginning of the operation.
        yield self._CreateNewEpisodesAndPosts(self._new_ep_dicts,
                                              self._new_ids)

        # Create cover photo if one is needed.
        if self._need_cover_photo:
            self._viewpoint.cover_photo = Viewpoint.SelectCoverPhotoFromEpDicts(
                self._new_ep_dicts)
            yield gen.Task(self._viewpoint.Update, self._client)
Beispiel #9
0
    def testViewpointAttributes(self):
        """Set all possible attributes, then as few attributes as possible."""
        # CreateViewpointDict already sets most of the attributes.
        vp_dict = self._CreateViewpointDict(self._cookie)
        self._tester.ShareNew(self._cookie,
                              [(self._episode_id, self._photo_ids)],
                              [{
                                  'user_id': self._user2.user_id
                              }], **vp_dict)

        viewpoint_id = Viewpoint.ConstructViewpointId(self._device_ids[0],
                                                      self._test_id)
        self._test_id += 1
        self._tester.ShareNew(self._cookie, [], [],
                              viewpoint_id=viewpoint_id,
                              type=Viewpoint.EVENT)
        viewpoint = self._RunAsync(Viewpoint.Query,
                                   self._client,
                                   vp_dict['viewpoint_id'],
                                   col_names=None)
        self.assertEqual(viewpoint.cover_photo['photo_id'], self._photo_ids[0])
Beispiel #10
0
 def _SetCoverPhotoOnViewpoint(self, viewpoint_id, episode_id, photo_id):
   """Updates a viewpoint with the given selected cover_photo."""
   viewpoint = self._RunAsync(Viewpoint.Query, self._client, viewpoint_id, None)
   viewpoint.cover_photo = Viewpoint.ConstructCoverPhoto(episode_id, photo_id)
   self._RunAsync(viewpoint.Update, self._client)
Beispiel #11
0
    def _Check(self):
        """Gathers pre-mutation information:
       1. Checkpoints list of contacts that need to be made prospective users.
       2. Cover photo, if not specified.

       Validates the following:
       1. Max follower limit.
       2. Permissions to share from source episodes.
       3. Permission to create new viewpoint.
       4. Cover photo is contained in the request.

    Returns True if all checks succeeded and operation execution should continue, or False
    if the operation should end immediately.
    """
        if len(
                self._contact_dicts
        ) + 1 > Viewpoint.MAX_FOLLOWERS:  # +1 to account for user creating viewpoint.
            raise LimitExceededError(
                'User %d attempted to exceed follower limit on viewpoint "%s" by creating a viewpoint with %d followers.'
                % (self._user_id, self._viewpoint_id,
                   len(self._contact_dicts) + 1))

        # Validate source episodes and posts.
        source_ep_posts_list = yield ViewfinderOperation._CheckCopySources(
            'share', self._client, self._user_id, self._ep_dicts)

        # Get dicts describing the target episodes and posts.
        target_ep_ids = [
            ep_dict['new_episode_id'] for ep_dict in self._ep_dicts
        ]
        self._new_ep_dicts = ViewfinderOperation._CreateCopyTargetDicts(
            self._op.timestamp, self._user_id, self._viewpoint_id,
            source_ep_posts_list, target_ep_ids)

        # Does request explicitly set a cover photo?
        if self._vp_dict.has_key('cover_photo'):
            if self._vp_dict['type'] == Viewpoint.DEFAULT:
                # cover_photo isn't supported creating default viewpoint.
                raise InvalidRequestError(
                    'cover_photo is invalid in share_new request for default viewpoint.'
                )
            # Make sure the designated cover photo is contained in the request.
            elif not Viewpoint.IsCoverPhotoContainedInEpDicts(
                    self._vp_dict['cover_photo']['episode_id'],
                    self._vp_dict['cover_photo']['photo_id'],
                    self._new_ep_dicts):
                logging.warning(
                    'cover_photo is specified but not contained in request: vp_dict: %s, ep_dicts: %s',
                    self._vp_dict, self._ep_dicts)
                raise InvalidRequestError(
                    'cover_photo is specified but not contained in request.')
        else:
            # Select cover photo from the set being shared.
            self._vp_dict[
                'cover_photo'] = Viewpoint.SelectCoverPhotoFromEpDicts(
                    self._new_ep_dicts)

        # Start populating the checkpoint if this the first time the operation has been run.
        if self._op.checkpoint is None:
            # If viewpoint already exists, then just warn and do nothing. We do not raise an error
            # because sometimes the client resubmits the same operation with different ids.
            viewpoint = yield gen.Task(Viewpoint.Query,
                                       self._client,
                                       self._viewpoint_id,
                                       None,
                                       must_exist=False)
            if viewpoint is not None:
                logging.warning('target viewpoint "%s" already exists',
                                self._viewpoint_id)
                raise gen.Return(False)

            # Get a tuple for each contact: (user_exists?, user_id, webapp_dev_id).
            self._contact_ids = yield self._ResolveContactIds(
                self._contact_dicts)

            # Set checkpoint.
            # List of contacts need to be check-pointed because it may change in the UPDATE phase (when contacts
            # can be bound to prospective users). If we fail after UPDATE, but before NOTIFY, we would not send
            # correct notifications on retry.
            checkpoint = {'contacts': self._contact_ids}
            yield self._op.SetCheckpoint(self._client, checkpoint)
        else:
            # Restore state from checkpoint.
            self._contact_ids = self._op.checkpoint['contacts']

        self._contact_user_ids = [
            user_id
            for user_exists, user_id, webapp_dev_id in self._contact_ids
        ]

        raise gen.Return(True)
    def _NotifyFollowers(cls,
                         client,
                         viewpoint_id,
                         followers,
                         invalidate,
                         activity_func,
                         inc_badge=False,
                         always_notify=False):
        """Adds a notification for each of the given followers that the specified viewpoint has
    structurally changed. If "invalidate" is a dict, then uses that directly. Otherwise, assumes
    it's a function that takes a follower id and returns the invalidate dict for that follower.
    If "always_notify" is true, then always send notifications, even to removed followers.

    In order to minimize undesirable client artifacts caused by reads of half-committed data,
    we will commit updates in this order:

    1. In parallel:
       a. Create the activity.
       b. Update all Followed records (of all followers in the viewpoint).

    2. In parallel:
       a. Update update_seq in the viewpoint.
       b. Update viewed_seq in the sending follower.

    3. In parallel:
       a. For each follower:
          i. Create notification
          ii. Send alert
    """
        from viewfinder.backend.db.viewpoint import Viewpoint
        from viewfinder.backend.op.alert_manager import AlertManager

        # Get the current operation, which provides the calling user and the op timestamp.
        operation = NotificationManager._GetOperation()

        @gen.coroutine
        def _NotifyOneFollower(viewpoint, seq_num_pair, activity, follower,
                               follower_settings):
            """Creates a notification for the follower and sends an alert if configured to do so."""
            # If follower has been removed, do not send notifications or alerts to it.
            if follower.IsRemoved() and not always_notify:
                return

            # Get the invalidate dict.
            if invalidate is None or isinstance(invalidate, dict):
                foll_invalidate = invalidate
            else:
                foll_invalidate = invalidate(follower.user_id)

            # Don't send alert or increment badge for the user that is creating the activity and
            # sending the NotificationManager.
            is_sending_user = follower.user_id == operation.user_id

            # Create the notification for the follower.
            # Update the Followed index, which orders viewpoints by timestamp of last update.
            notification = yield Notification.CreateForUser(
                client,
                operation,
                follower.user_id,
                activity.name,
                invalidate=foll_invalidate,
                activity_id=activity.activity_id,
                viewpoint_id=viewpoint_id,
                seq_num_pair=seq_num_pair,
                inc_badge=inc_badge and not is_sending_user)

            if not is_sending_user:
                yield AlertManager.SendFollowerAlert(client, follower.user_id,
                                                     notification.badge,
                                                     viewpoint, follower,
                                                     follower_settings,
                                                     activity)

        # We want a locked viewpoint while updating its sequence numbers and the corresponding Followed rows.
        # Locking also prevents race conditions where new followers are added during iteration.
        Viewpoint.AssertViewpointLockAcquired(viewpoint_id)

        # Get affected viewpoint and the follower sending the notification, if it's available.
        viewpoint = yield gen.Task(Viewpoint.Query, client, viewpoint_id, None)
        sending_follower = next((follower for follower in followers
                                 if follower.user_id == operation.user_id),
                                None)

        # Update the viewpoint and follower sequence numbers, but do not commit until after Followed
        # records are updated (since we're updating the viewpoint's "last_updated" at that time anyway).
        viewpoint.update_seq += 1
        if sending_follower is not None:
            sending_follower.viewed_seq += 1
            seq_num_pair = (viewpoint.update_seq, sending_follower.viewed_seq)
        else:
            seq_num_pair = (viewpoint.update_seq, None)

        # Create the activity.
        activity_task = activity_func(client, operation.user_id, viewpoint_id,
                                      viewpoint.update_seq)

        # Get account settings for each follower in order to determine what level of alerts they'd like.
        follower_keys = [
            AccountSettings.ConstructKey(follower.user_id)
            for follower in followers
        ]
        settings_task = gen.Task(AccountSettings.BatchQuery,
                                 client,
                                 follower_keys,
                                 None,
                                 must_exist=False)

        # Update all Followed records.
        followed_task = gen.Multi([
            gen.Task(Followed.UpdateDateUpdated, client, follower.user_id,
                     viewpoint_id, viewpoint.last_updated, operation.timestamp)
            for follower in followers
        ])

        activity, all_follower_settings, _ = yield [
            activity_task, settings_task, followed_task
        ]

        # Now that the Followed records have been updated, update the viewpoint's "last_updated" attribute.
        # This must be done afterwards so that the previous value of last_updated is known, even if the
        # operation fails and restarts.
        if operation.timestamp > viewpoint.last_updated:
            viewpoint.last_updated = operation.timestamp

        # Commit changes to update_seq and the sending follower's viewed_seq.
        yield [
            gen.Task(viewpoint.Update, client),
            gen.Task(sending_follower.Update, client)
            if sending_follower is not None else util.GenConstant(None)
        ]

        # Visit each follower and generate notifications and alerts for it.
        yield [
            _NotifyOneFollower(viewpoint, seq_num_pair, activity, follower,
                               follower_settings) for follower,
            follower_settings in zip(followers, all_follower_settings)
        ]
  def _CreateWelcomeConversation(self):
    """Creates the welcome conversation at the db level. Operations are not used in order
    to avoid creating notifications, sending alerts, taking locks, running nested operations,
    etc.
    """
    from viewfinder.backend.www.system_users import NARRATOR_USER
    from viewfinder.backend.www.system_users import NARRATOR_UPLOAD_PHOTOS, NARRATOR_UPLOAD_PHOTOS_2, NARRATOR_UPLOAD_PHOTOS_3

    # Accumulate accounting changes.
    self._acc_accum = AccountingAccumulator()

    self._unique_id = self._unique_id_start
    self._update_seq = 1

    # Create the viewpoint.
    self._viewpoint_id = Viewpoint.ConstructViewpointId(self._new_user.webapp_dev_id, self._unique_id)
    self._unique_id += 1
    initial_follower_ids = [self._new_user.user_id]
    viewpoint, followers = yield Viewpoint.CreateNewWithFollowers(self._client,
                                                                  follower_ids=initial_follower_ids,
                                                                  user_id=NARRATOR_USER.user_id,
                                                                  viewpoint_id=self._viewpoint_id,
                                                                  type=Viewpoint.SYSTEM,
                                                                  title='Welcome...',
                                                                  timestamp=self._op.timestamp)

    # Narrator creates and introduces the conversation.
    yield self._CreateActivity(NARRATOR_USER,
                               self._op.timestamp - 60,
                               Activity.CreateShareNew,
                               ep_dicts=[],
                               follower_ids=initial_follower_ids)

    yield self._PostComment(NARRATOR_USER,
                            self._op.timestamp - 60,
                            'Welcome to Viewfinder, a new way to privately share photos with your friends.')

    # Narrator shares photos.
    yield self._PostComment(NARRATOR_USER,
                            self._op.timestamp - 59,
                            'Select as many photos as you want to share with exactly who you want.')

    photo_ids = [ph_dict['photo_id'] for ph_dict in NARRATOR_UPLOAD_PHOTOS['photos']]
    episode = yield self._CreateEpisodeWithPosts(NARRATOR_USER,
                                                 NARRATOR_UPLOAD_PHOTOS['episode']['episode_id'],
                                                 NARRATOR_UPLOAD_PHOTOS['photos'])
    yield self._CreateActivity(NARRATOR_USER,
                               self._op.timestamp - 58,
                               Activity.CreateShareExisting,
                               ep_dicts=[{'new_episode_id': episode.episode_id, 'photo_ids': photo_ids}])

    # Set cover photo on viewpoint now that episode id is known.
    viewpoint.cover_photo = {'episode_id': episode.episode_id,
                             'photo_id': NARRATOR_UPLOAD_PHOTOS['photos'][0]['photo_id']}
    yield gen.Task(viewpoint.Update, self._client)

    yield self._PostComment(NARRATOR_USER,
                            self._op.timestamp - 56,
                            'Your friends can also add photos to the conversation, '
                            'creating unique collaborative albums.')

    yield self._PostComment(NARRATOR_USER,
                            self._op.timestamp - 55,
                            'You can add as many photos, messages and friends as you want to the conversation, '
                            'leading to a memorable shared experience.')

    # Narrator shares more photos.
    photo_ids = [ph_dict['photo_id'] for ph_dict in NARRATOR_UPLOAD_PHOTOS_2['photos']]
    episode = yield self._CreateEpisodeWithPosts(NARRATOR_USER,
                                                 NARRATOR_UPLOAD_PHOTOS_2['episode']['episode_id'],
                                                 NARRATOR_UPLOAD_PHOTOS_2['photos'])
    yield self._CreateActivity(NARRATOR_USER,
                               self._op.timestamp - 54,
                               Activity.CreateShareExisting,
                               ep_dicts=[{'new_episode_id': episode.episode_id, 'photo_ids': photo_ids}])

    # Single-photo comment.
    yield self._PostComment(NARRATOR_USER,
                            self._op.timestamp - 53,
                            'Hold and press on photos to comment on specific pics.',
                            asset_id=NARRATOR_UPLOAD_PHOTOS_2['photos'][1]['photo_id'])

    # Narrator rambles on for a while.
    yield self._PostComment(NARRATOR_USER,
                            self._op.timestamp - 52,
                            'Use mobile #\'s or email addresses to add new people if they\'re not yet on Viewfinder.');

    # Narrator shares more photos.
    photo_ids = [ph_dict['photo_id'] for ph_dict in NARRATOR_UPLOAD_PHOTOS_3['photos']]
    episode = yield self._CreateEpisodeWithPosts(NARRATOR_USER,
                                                 NARRATOR_UPLOAD_PHOTOS_3['episode']['episode_id'],
                                                 NARRATOR_UPLOAD_PHOTOS_3['photos'])
    yield self._CreateActivity(NARRATOR_USER,
                               self._op.timestamp - 51,
                               Activity.CreateShareExisting,
                               ep_dicts=[{'new_episode_id': episode.episode_id, 'photo_ids': photo_ids}])


    # Conclusion.
    yield self._PostComment(NARRATOR_USER,
                            self._op.timestamp - 50,
                            'Viewfinder is perfect for vacations, weddings, or any shared experience where you want '
                            'to share photos without posting them for everyone to see.')

    yield self._PostComment(NARRATOR_USER,
                            self._op.timestamp - 49,
                            'Start sharing now.')

    # Validate that we allocated enough ids and counted update_seq properly.
    assert self._unique_id == self._unique_id_start + CreateProspectiveOperation._ASSET_ID_COUNT, self._unique_id
    assert self._update_seq == CreateProspectiveOperation._UPDATE_SEQ_COUNT, self._update_seq

    # Set update_seq on the new viewpoint.
    viewpoint.update_seq = self._update_seq
    yield gen.Task(viewpoint.Update, self._client)

    # Remove this viewpoint for all sample users so that accounting will be correct (also in case
    # we want to sync a device to Nick's account and see if new users are trying to chat). Also
    # update viewed_seq so that entire conversation is "read" for each sample user.
    for follower in followers:
      if follower.user_id != self._new_user.user_id:
        follower.viewed_seq = viewpoint.update_seq
        yield follower.RemoveViewpoint(self._client)

    # Commit accounting changes.
    yield self._acc_accum.Apply(self._client)
Beispiel #14
0
  def ValidateUpdateUser(self, name, op_dict, user_dict, ident_dict,
                         device_dict=None, is_prospective=False):
    """Validates that a user and identity have been created in the database
    if they did not already exist, or were updated if they did. If
    "device_dict" is defined, validates that a device was created or updated
    as well.
    """
    user_id = user_dict['user_id']

    # Validate creation of the default viewpoint, follower, and followed record.
    viewpoint_id = Viewpoint.ConstructViewpointId(user_dict['webapp_dev_id'], 0)
    viewpoint = self.GetModelObject(User, user_id, must_exist=False)
    if viewpoint is None:
      expected_viewpoint = self.ValidateCreateDBObject(Viewpoint,
                                                       viewpoint_id=viewpoint_id,
                                                       user_id=user_id,
                                                       timestamp=op_dict['op_timestamp'],
                                                       last_updated=op_dict['op_timestamp'],
                                                       type=Viewpoint.DEFAULT,
                                                       update_seq=0)

      labels = Follower.PERMISSION_LABELS + [Follower.PERSONAL]
      expected_follower = self.ValidateFollower(user_id=user_id,
                                                viewpoint_id=viewpoint_id,
                                                timestamp=op_dict['op_timestamp'],
                                                labels=labels,
                                                last_updated=op_dict['op_timestamp'],
                                                viewed_seq=0)

    # Validate User object.
    scratch_user_dict = deepcopy(user_dict)
    if ident_dict.get('authority', None) == 'Facebook' and user_dict.get('email', None):
      scratch_user_dict['facebook_email'] = user_dict['email']

    union_label = [] if is_prospective else [User.REGISTERED]
    existing_user = self.GetModelObject(User, user_id, must_exist=False)
    if existing_user is None:
      is_registering = False
      before_user_dict = None
      scratch_user_dict['private_vp_id'] = viewpoint_id
      scratch_user_dict['labels'] = union_label
    else:
      is_registering = not existing_user.IsRegistered()
      before_user_dict = existing_user._asdict()
      scratch_user_dict.update(before_user_dict)
      scratch_user_dict['labels'] = list(set(scratch_user_dict['labels']).union(union_label))

    expected_user = self.ValidateUpdateDBObject(User, **scratch_user_dict)

    # Validate AccountSettings object.
    settings = AccountSettings.CreateForUser(user_id)

    if device_dict is None:
      if self.GetModelObject(AccountSettings, settings.GetKey(), must_exist=False) is None:
        # First web device was registered, so validate that emails or sms messages are turned on.
        settings.push_alerts = AccountSettings.PUSH_NONE
        settings.email_alerts = AccountSettings.EMAIL_NONE
        settings.sms_alerts = AccountSettings.SMS_NONE

        identity_type, identity_value = Identity.SplitKey(ident_dict['key'])
        if identity_type == 'Email':
          settings.email_alerts = AccountSettings.EMAIL_ON_SHARE_NEW
        elif identity_type == 'Phone':
          settings.sms_alerts = AccountSettings.SMS_ON_SHARE_NEW
    else:
      if len(self.QueryModelObjects(Device, user_id)) == 0:
        # First mobile device was registered, so validate that emails and sms messages are
        # turned off and push alerts turned on.
        settings.push_alerts = AccountSettings.PUSH_ALL
        settings.email_alerts = AccountSettings.EMAIL_NONE
        settings.sms_alerts = AccountSettings.SMS_NONE

    self.ValidateUpdateDBObject(AccountSettings, **settings._asdict())

    # Validate Friend object.
    self.ValidateUpdateDBObject(Friend, user_id=user_id, friend_id=user_id)

    # Validate Identity object.
    existing_identity = self.GetModelObject(Identity, ident_dict['key'], must_exist=False)
    expected_ident = self.ValidateUpdateDBObject(Identity,
                                                 user_id=user_id,
                                                 **ident_dict)

    # Validate Device object.
    if device_dict is not None:
      update_dict = {'user_id': user_id,
                     'timestamp': util._TEST_TIME,
                     'last_access': util._TEST_TIME}
      if 'push_token' in device_dict:
        update_dict['alert_user_id'] = user_id
      update_dict.update(device_dict)
      expected_device = self.ValidateUpdateDBObject(Device, **update_dict)

      # Validate that any other devices with same push token have had their tokens revoked.
      if 'push_token' in device_dict:
        predicate = lambda d: d.device_id != expected_device.device_id and d.push_token == expected_device.push_token
        other_devices = self.QueryModelObjects(Device, predicate=predicate)
        for device in other_devices:
          self.ValidateUpdateDBObject(Device,
                                      user_id=device.user_id,
                                      device_id=device.device_id,
                                      push_token=None,
                                      alert_user_id=None)

    # Validate Contact objects.
    if existing_identity is None or is_registering:
      self.ValidateRewriteContacts(expected_ident.key, op_dict)

    # Validate contact notifications.
    self.ValidateContactNotifications(name, expected_ident.key, op_dict)

    # Validate Friend notifications.
    after_user_dict = self.GetModelObject(User, user_id)._asdict()
    if before_user_dict != after_user_dict and not is_prospective:
      invalidate = {'users': [user_id]}
      self.ValidateFriendNotifications('register friend', user_id, op_dict, invalidate)

    # Validate analytics entry for Register.
    if existing_user is None:
      # User is being created for the first time, it must have a CREATE_PROSPECTIVE analytics entry.
      analytics = Analytics.Create(entity='us:%d' % user_id,
                                   type=Analytics.USER_CREATE_PROSPECTIVE)
      self.ValidateCreateDBObject(Analytics, **analytics._asdict())

    if (not existing_user or is_registering) and not is_prospective:
      # User is being registered.
      analytics = Analytics.Create(entity='us:%d' % user_id,
                                   type=Analytics.USER_REGISTER)
      self.ValidateCreateDBObject(Analytics, **analytics._asdict())
    def testAssetIdAltDevice(self):
        """Test construction of assets using a different device than the calling device."""
        # ------------------------------
        # Try to upload using a device not owned by the user at all.
        # ------------------------------
        ep_dict = self._CreateEpisodeDict(self._cookie)
        ep_dict['episode_id'] = Episode.ConstructEpisodeId(
            time.time(), self._device_ids[2], self._test_id)
        self._test_id += 1

        ph_dict = self._CreatePhotoDict(self._cookie)
        ph_dict['photo_id'] = Photo.ConstructPhotoId(time.time(),
                                                     self._device_ids[2],
                                                     self._test_id)
        self._test_id += 1

        self.assertRaisesHttpError(403, self._tester.UploadEpisode,
                                   self._cookie, ep_dict, [ph_dict])

        # ------------------------------
        # Upload using alternate devices owned by the user.
        # ------------------------------
        ep_dict = self._CreateEpisodeDict(self._cookie)
        ep_dict['episode_id'] = Episode.ConstructEpisodeId(
            time.time(), self._extra_device_id1, self._test_id)
        self._test_id += 1

        ph_dict = self._CreatePhotoDict(self._cookie)
        ph_dict['photo_id'] = Photo.ConstructPhotoId(time.time(),
                                                     self._extra_device_id2,
                                                     self._test_id)
        self._test_id += 1

        act_dict = self._tester.CreateActivityDict(self._cookie)
        act_dict['activity_id'] = Activity.ConstructActivityId(
            time.time(), self._extra_device_id1, self._test_id)
        self._test_id += 1

        self._tester.UploadEpisode(self._cookie, ep_dict, [ph_dict], act_dict)

        # ------------------------------
        # Share to a new viewpoint using alternate devices owned by the user.
        # ------------------------------
        viewpoint_id = Viewpoint.ConstructViewpointId(self._extra_device_id2,
                                                      self._test_id)
        self._test_id += 1

        self._tester.ShareNew(self._cookie,
                              [(ep_dict['episode_id'], [ph_dict['photo_id']])],
                              [self._user2.user_id],
                              viewpoint_id=viewpoint_id)

        # ------------------------------
        # Post to the new viewpoint using alternate devices owned by the user.
        # ------------------------------
        comment_id = Comment.ConstructCommentId(time.time(),
                                                self._extra_device_id1,
                                                self._test_id)
        self._test_id += 1

        self._tester.PostComment(self._cookie, viewpoint_id, 'hi')
Beispiel #16
0
    def testWelcomeConversation(self):
        """Test that welcome conversation is created for new users."""
        # Turn off validation, since welcome conversation is too large to validate using model.
        self._validate = False
        validator = self._tester.validator

        # Ensure that system users are created.
        self._RunAsync(CreateSystemUsers, self._client)

        # Trigger creation of two prospective users.
        request_dict = {
            'activity':
            self._tester.CreateActivityDict(self._cookie),
            'viewpoint_id':
            self._vp_id,
            'contacts':
            self._tester.CreateContactDicts([
                'Email:[email protected]',
                'Email:[email protected]'
            ])
        }
        response_dict = self._tester.SendRequest('add_followers', self._cookie,
                                                 request_dict)

        identity = self._RunAsync(Identity.Query, self._client,
                                  'Email:[email protected]', None)
        user = self._RunAsync(User.Query, self._client, identity.user_id, None)

        # Validate the viewpoint.
        welcome_vp_id = Viewpoint.ConstructViewpointId(user.webapp_dev_id, 1)
        viewpoint = self._RunAsync(Viewpoint.Query, self._client,
                                   welcome_vp_id, None)

        query_expr = ('episode.parent_ep_id={id}', {
            'id':
            system_users.NARRATOR_UPLOAD_PHOTOS['episode']['episode_id']
        })
        cover_photo_ep = self._RunAsync(Episode.IndexQuery, self._client,
                                        query_expr, None)[0]
        cover_photo = {
            'episode_id':
            cover_photo_ep.episode_id,
            'photo_id':
            system_users.NARRATOR_UPLOAD_PHOTOS['photos'][0]['photo_id']
        }

        validator.ValidateUpdateDBObject(
            Viewpoint,
            viewpoint_id=welcome_vp_id,
            user_id=system_users.NARRATOR_USER.user_id,
            title='Welcome...',
            type=Viewpoint.SYSTEM,
            timestamp=util._TEST_TIME,
            update_seq=13,
            cover_photo=cover_photo)

        # Validate the followers.
        followers, _ = self._RunAsync(Viewpoint.QueryFollowers, self._client,
                                      welcome_vp_id)
        for follower in followers:
            # All but the new user should have removed the viewpoint and have viewed_seq = update_seq.
            if follower.user_id != user.user_id:
                self.assertTrue(follower.IsRemoved())
                self.assertEqual(follower.viewed_seq, viewpoint.update_seq)
            else:
                self.assertTrue(not follower.IsRemoved())
                self.assertEqual(follower.viewed_seq, 0)

        # Validate the episodes and photos.
        episodes, _ = self._RunAsync(Viewpoint.QueryEpisodes, self._client,
                                     welcome_vp_id)
        self.assertEqual(len(episodes), 3)

        num_posts = sum(
            len(
                self._RunAsync(Post.RangeQuery, self._client,
                               episode.episode_id, None, None, None))
            for episode in episodes)
        self.assertEqual(num_posts, 9)

        cookie = self._GetSecureUserCookie(user, user.webapp_dev_id)
        vp_select = self._tester.CreateViewpointSelection(welcome_vp_id)
        response_dict = self._tester.SendRequest('query_viewpoints', cookie,
                                                 {'viewpoints': [vp_select]})
        self.assertEqual(len(response_dict['viewpoints'][0]['followers']), 2)
        self.assertEqual(len(response_dict['viewpoints'][0]['activities']), 12)
        self.assertEqual(len(response_dict['viewpoints'][0]['comments']), 8)
        self.assertEqual(len(response_dict['viewpoints'][0]['episodes']), 3)

        ep_select_list = [
            self._tester.CreateEpisodeSelection(ep_dict['episode_id'])
            for ep_dict in response_dict['viewpoints'][0]['episodes']
        ]
        response_dict = self._tester.SendRequest('query_episodes', cookie,
                                                 {'episodes': ep_select_list})

        self.assertEqual(
            len([
                ph_dict for ep_dict in response_dict['episodes']
                for ph_dict in ep_dict['photos']
            ]), 9)

        # Validate the accounting.
        validator.ValidateAccounting()
Beispiel #17
0
 def testRepr(self):
   vp = Viewpoint.CreateFromKeywords(viewpoint_id='vp1', title='hello')
   self.assertIn('vp1', repr(vp))
   self.assertNotIn('hello', repr(vp))