Пример #1
0
    def get_group_notifications(self,
                                group: Dict[str, str],
                                count: int = 10,
                                include_seen: bool = False,
                                level=None,
                                verb=None,
                                reverse: bool = False) -> dict:
        """
        Returns all notifications (using the get_notifications fn.) for a user, filtered down to
        those that reference an Entity of type group with the given id.
        """
        notes = self.get_notifications(count=count,
                                       include_seen=include_seen,
                                       level=level,
                                       verb=verb,
                                       reverse=reverse)
        # group has id and name keys
        group_notes = {"unseen": 0, "name": group.get("name"), "feed": list()}
        gid = group["id"]

        def is_group(e: Entity) -> bool:
            return e.id == gid and e.type == "group"

        notes_list = list()
        for n in notes["feed"]:
            if is_group(n.actor) or is_group(n.object) or any(
                [is_group(t) for t in n.target]):
                notes_list.append(n)
                if not n.seen:
                    group_notes["unseen"] += 1
        Notification.update_entity_names(notes_list, token=self.token)
        for n in notes_list:
            group_notes["feed"].append(n.user_view())
        return group_notes
Пример #2
0
def test_validate_ok(requests_mock):
    user_id = "foo"
    user_display = "Foo Bar"
    requests_mock.get('{}/api/V2/users?list={}'.format(cfg.get('feeds', 'auth-url'), user_id), text=json.dumps({user_id: user_display}))
    note = Notification(user_id, verb_inf, note_object, source)
    # If this doesn't throw any errors, then it passes!
    note.validate()
Пример #3
0
def test_note_new_bad_level():
    with pytest.raises(AssertionError) as e:
        Notification(actor, verb_inf, note_object, source, level=None)
    assert "level must not be None" in str(e.value)

    with pytest.raises(MissingLevelError) as e:
        Notification(actor, verb_inf, note_object, source, level="foobar")
    assert 'Level "foobar" not found' in str(e.value)
Пример #4
0
def test_note_new_bad_verb():
    with pytest.raises(AssertionError) as e:
        Notification(actor, None, note_object, source)
    assert "verb must not be None" in str(e.value)

    with pytest.raises(MissingVerbError) as e:
        Notification(actor, "foobar", note_object, source)
    assert 'Verb "foobar" not found' in str(e.value)
Пример #5
0
def test_note_new_bad_expires():
    bad_expires = ["foo", {}, []]
    for bad in bad_expires:
        with pytest.raises(InvalidExpirationError) as e:
            Notification(actor, verb_inf, note_object, source, expires=bad)
        assert "Expiration time should be the number of milliseconds" in str(e.value)
    bad_expires = [123, True, False]
    for bad in bad_expires:
        with pytest.raises(InvalidExpirationError) as e:
            Notification(actor, verb_inf, note_object, source, expires=bad)
        assert "Notifications should expire sometime after they are created" in str(e.value)
Пример #6
0
def test_from_dict_missing_keys():
    d = {
        "actor": actor
    }
    with pytest.raises(InvalidNotificationError) as e:
        Notification.from_dict(d)
    assert "Missing keys" in str(e.value)

    with pytest.raises(InvalidNotificationError) as e:
        Notification.from_dict(None)
    assert "Can only run 'from_dict' on a dict" in str(e.value)
Пример #7
0
def test_to_dict():
    note = Notification(actor, verb_inf, note_object, source, level=level_name)
    d = note.to_dict()
    assert d["actor"] == actor_d
    assert d["verb"] == verb_id
    assert d["object"] == object_d
    assert d["source"] == source
    assert isinstance(d["expires"], int) and d["expires"] == note.expires
    assert isinstance(d["created"], int) and d["created"] == note.created
    assert d["target"] == []
    assert d["context"] == {}
    assert d["level"] == level_id
    assert d["external_key"] is None
    assert d["users"] == []
Пример #8
0
def test_user_view():
    note = Notification(actor, verb_inf, note_object, source, level=level_id)
    v = note.user_view()
    assert v["actor"] == actor_d
    assert v["verb"] == verb_past
    assert v["object"] == object_d
    assert v["source"] == source
    assert isinstance(v["expires"], int) and v["expires"] == note.expires
    assert isinstance(v["created"], int) and v["created"] == note.created
    assert v["target"] == []
    assert v["context"] == {}
    assert v["level"] == level_name
    assert "external_key" not in v
    assert "users" not in v
Пример #9
0
def test_deserialization():
    note = Notification(actor, verb_inf, note_object, source, level=level_id,
                        target=target, external_key=external_key, context=context,
                        users=users)
    serial = note.serialize()
    note2 = Notification.deserialize(serial)
    assert note2.id == note.id
    assert note2.actor == note.actor
    assert note2.verb.id == note.verb.id
    assert note2.object == note.object
    assert note2.source == note.source
    assert note2.level.id == note.level.id
    assert note2.target == note.target
    assert note2.external_key == note.external_key
    assert note2.context == note.context
    assert note2.users == note.users
Пример #10
0
 def get_activities(self,
                    count=10,
                    include_seen=False,
                    level=None,
                    verb=None,
                    reverse=False,
                    user_view=False) -> List[Notification]:
     """
     Returns a selection of activities.
     :param count: Maximum number of Notifications to return (default 10)
     """
     # steps.
     # 0. If in cache, return them.  <-- later
     # 1. Get storage adapter.
     # 2. Query it for recent activities from this user.
     # 3. Cache them here.
     # 4. Return them.
     if count < 1 or not isinstance(count, int):
         raise ValueError("Count must be an integer > 0")
     serial_notes = self.timeline_storage.get_timeline(
         count=count,
         include_seen=include_seen,
         level=level,
         verb=verb,
         reverse=reverse)
     note_list = list()
     user_dict = self.user.to_dict()
     for note in serial_notes:
         if user_dict in note["unseen"]:
             note["seen"] = False
         else:
             note["seen"] = True
         note_list.append(Notification.from_dict(note, self.token))
     return note_list
Пример #11
0
def test_serialization():
    note = Notification(actor, verb_inf, note_object, source, level=level_id)
    serial = note.serialize()
    json_serial = json.loads(serial)
    assert "i" in json_serial
    assert_is_uuid(json_serial['i'])
    assert "a" in json_serial and json_serial['a'] == str(actor)
    assert "v" in json_serial and json_serial['v'] == verb_id
    assert "o" in json_serial and json_serial['o'] == str(note_object)
    assert "s" in json_serial and json_serial['s'] == source
    assert "l" in json_serial and json_serial['l'] == level_id
    assert "c" in json_serial and json_serial['c'] == note.created
    assert "e" in json_serial and json_serial['e'] == note.expires
    assert "n" in json_serial and json_serial['n'] == {}
    assert "x" in json_serial and json_serial['x'] == None
    assert "t" in json_serial and json_serial['t'] == []
    assert "u" in json_serial and json_serial['u'] == []
Пример #12
0
def test_note_new_diff_levels():
    assert_args = {
        "actor": actor,
        "verb_inf": verb_inf,
        "object": note_object,
        "source": source
    }
    for name in ['alert', 'warning', 'request', 'error']:
        note = Notification(actor, verb_inf, note_object, source, level=name)
        test_args = assert_args.copy()
        test_args['level_name'] = name
        assert_note_ok(note, **test_args)
    for id_ in ['1', '2', '3', '4']:
        note = Notification(actor, verb_inf, note_object, source, level=id_)
        test_args = assert_args.copy()
        test_args['level_id'] = id_
        assert_note_ok(note, **test_args)
Пример #13
0
def test_serialization_all_kwargs():
    note = Notification(actor, verb_inf, note_object, source, level=level_id,
                        target=target, external_key=external_key, context=context, users=users)
    serial = note.serialize()
    json_serial = json.loads(serial)
    assert "i" in json_serial
    assert_is_uuid(json_serial['i'])
    assert "a" in json_serial and json_serial['a'] == str(actor)
    assert "v" in json_serial and json_serial['v'] == verb_id
    assert "o" in json_serial and json_serial['o'] == str(note_object)
    assert "s" in json_serial and json_serial['s'] == source
    assert "l" in json_serial and json_serial['l'] == level_id
    assert "c" in json_serial and json_serial['c'] == note.created
    assert "e" in json_serial and json_serial['e'] == note.expires
    assert "n" in json_serial and json_serial['n'] == context
    assert "x" in json_serial and json_serial['x'] == external_key
    assert "t" in json_serial and json_serial['t'] == [str(target[0])]
    assert "u" in json_serial and json_serial['u'] == [str(users[0])]
Пример #14
0
 def get_notifications(self,
                       count: int = 10,
                       include_seen: bool = False,
                       level=None,
                       verb=None,
                       reverse: bool = False,
                       user_view: bool = False) -> dict:
     """
     Fetches all activities matching the requested inputs.
     :param count: max number of most recent notifications to return. default=10
     :param include_seen: include notifications that have been seen in the response.
         default = False
     :param level: if not None, will only return notifications of the given level.
         default = None
     :param verb: if not None, will only return notifications made with the given verb.
         default = None
     :param reverse: if True, will reverse the order of the result (default False)
     :param user_view: if True, will return the user_view dict version of each Notification
         object. If False, will return a list of Notification objects instead. default False
     :return: a dict with the requested notifications, and a key with the total number in the
         feed that are marked unseen
     :rtype: dict
     :raises ValueError: if count <= 0
     """
     activities = self.get_activities(count=count,
                                      include_seen=include_seen,
                                      verb=verb,
                                      level=level,
                                      reverse=reverse,
                                      user_view=user_view)
     ret_struct = {
         "unseen": self.get_unseen_count(),
         "name": self.user.name
     }
     if user_view:
         ret_struct["feed"] = list()
         Notification.update_entity_names(activities, token=self.token)
         for act in activities:
             ret_struct["feed"].append(act.user_view())
     else:
         ret_struct["feed"] = activities
     return ret_struct
Пример #15
0
 def get_notification(self, note_id):
     """
     Returns a single notification.
     If it doesn't exist (either the user can't see it, or it's really not there), raises
     a NotificationNotFoundError.
     """
     note = self.timeline_storage.get_single_activity_from_timeline(note_id)
     if note is None:
         raise NotificationNotFoundError(
             "Cannot find notification with id {}.".format(note_id))
     else:
         return Notification.from_dict(note, self.token)
Пример #16
0
def add_notification():
    """
    Adds a new notification for other users to see.
    Form data requires the following:
    * `actor` - a user or org id.
    * `actor_type` - either 'user' or 'group'
    * `type` - one of the type keywords (see below, TBD (as of 10/8))
    * `target` - optional, a user or org id. - always receives this notification
    * `object` - object of the notice. For invitations, the group to be invited to.
        For narratives, the narrative UPA.
    * `level` - alert, error, warning, or request.
    * `content` - optional, content of the notification, otherwise it'll be
        autogenerated from the info above.
    * `global` - true or false. If true, gets added to the global notification
        feed and everyone gets a copy.
    * `expires` - int, optional time to expire a notifications.
    * `source` - string, the source service behind the notification. Used in some logic
        to determine which feeds receive the notification

    This also requires a service token as an Authorization header.
    """
    token = get_auth_token(request)
    try:
        validate_service_token(token)
    except InvalidTokenError:
        if cfg.debug:
            if not is_feeds_admin(token):
                raise InvalidTokenError(
                    'Auth token must be either a Service token '
                    'or from a user with the FEEDS_ADMIN role!')
        else:
            raise
    log(__name__, request.get_data())
    params = parse_notification_params(json.loads(request.get_data()))
    # create a Notification from params.
    new_note = Notification(params.get('actor'),
                            params.get('verb'),
                            params.get('object'),
                            params.get('source'),
                            level=params.get('level'),
                            target=params.get('target', []),
                            context=params.get('context'),
                            expires=params.get('expires'),
                            external_key=params.get('external_key'),
                            users=params.get('users', []))
    # pass it to the NotificationManager to dole out to its audience feeds.
    manager = NotificationManager()
    manager.add_notification(new_note)
    # on success, return the notification id and info.
    return (flask.jsonify({'id': new_note.id}), 200)
Пример #17
0
def test_deserialize_bad():
    with pytest.raises(InvalidNotificationError) as e:
        Notification.deserialize(None)
    assert "Can't deserialize an input of 'None'" in str(e.value)

    with pytest.raises(InvalidNotificationError) as e:
        Notification.deserialize(json.dumps({'a': actor.to_dict()}))
    assert "Missing keys" in str(e.value)

    with pytest.raises(InvalidNotificationError) as e:
        Notification.deserialize("foo")
    assert "Can only deserialize a JSON string" in str(e.value)
Пример #18
0
def add_global_notification():
    token = get_auth_token(request)
    if not is_feeds_admin(token):
        raise InvalidTokenError(
            "You do not have permission to create a global notification!")

    params = parse_notification_params(json.loads(request.get_data()),
                                       is_global=True)
    new_note = Notification(Entity('kbase', 'admin'),
                            params.get('verb'),
                            Entity('kbase', 'admin'),
                            'kbase',
                            level=params.get('level'),
                            context=params.get('context'),
                            expires=params.get('expires'))
    global_feed = NotificationFeed(cfg.global_feed, cfg.global_feed_type)
    global_feed.add_notification(new_note)
    return (flask.jsonify({'id': new_note.id}), 200)
Пример #19
0
def test_from_dict():
    act_id = str(uuid.uuid4())
    verb = [verb_id, str(verb_id), verb_inf, verb_past]
    level = [level_id, level_name, str(level_id)]
    d = {
        "actor": actor_d,
        "object": object_d,
        "source": source,
        "expires": 1234567890111,
        "created": 1234567890000,
        "target": [target_d],
        "context": context,
        "external_key": external_key,
        "id": act_id,
        "users": [user_d]
    }
    # just being lazy and putting in the real Entity objects
    d_cmp = {
        "actor": actor,
        "object": note_object,
        "source": source,
        "expires": 1234567890111,
        "created": 1234567890000,
        "target": target,
        "context": context,
        "external_key": external_key,
        "id": act_id,
        "users": users
    }
    for v in verb:
        for l in level:
            note_d = d.copy()
            note_d_cmp = d_cmp.copy()
            note_d.update({'level': l, 'verb': v})
            note_d_cmp.update({'level': l, 'verb': v})
            note = Notification.from_dict(note_d)
            assert_note_ok(note, **note_d_cmp)
Пример #20
0
def test_note_new_bad_actor():
    # TODO: Should only fail on validate - shouldn't do a lookup whenever a new note is made.
    # also, shouldn't be None.
    with pytest.raises(AssertionError) as e:
        Notification(None, verb_inf, note_object, source)
    assert "actor must not be None" in str(e.value)
Пример #21
0
def test_note_new_bad_object():
    # TODO: Also test object validation itself later.
    with pytest.raises(AssertionError) as e:
        Notification(actor, verb_inf, None, source)
    assert 'note_object must not be None' in str(e.value)
Пример #22
0
def test_note_new_context():
    note = Notification(actor, verb_inf, note_object, source, context=context)
    assert_note_ok(note, actor=actor, verb_inf=verb_inf,
                   object=note_object, source=source, context=context)
Пример #23
0
def test_note_new_users():
    note = Notification(actor, verb_inf, note_object, source, users=users)
    assert_note_ok(note, actor=actor, verb_inf=verb_inf,
                   object=note_object, source=source, users=users)
Пример #24
0
def test_note_new_target():
    note = Notification(actor, verb_inf, note_object, source, target=target)
    assert_note_ok(note, actor=actor, verb_inf=verb_inf,
                   object=note_object, source=source, target=target)
Пример #25
0
def test_note_new_bad_source():
    # TODO: Validate sources as being real.
    with pytest.raises(AssertionError) as e:
        Notification(actor, verb_inf, note_object, None)
    assert 'source must not be None' in str(e.value)
Пример #26
0
def test_note_new_ok_no_kwargs():
    note = Notification(actor, verb_inf, note_object, source)
    assert_note_ok(note, actor=actor, verb_inf=verb_inf, object=note_object, source=source)
Пример #27
0
def test_note_new_bad_users():
    bad_users = [{}, "foo", 123, False]
    for bad in bad_users:
        with pytest.raises(AssertionError) as e:
            Notification(actor, verb_inf, note_object, source, users=bad)
        assert "users must be either a list or None" in str(e.value)
Пример #28
0
def test_note_new_bad_context():
    bad_context = [[], "foo", 123, False]
    for bad in bad_context:
        with pytest.raises(AssertionError) as e:
            Notification(actor, verb_inf, note_object, source, context=bad)
        assert "context must be either a dict or None" in str(e.value)
Пример #29
0
def test_default_lifespan():
    note = Notification(actor, verb_inf, note_object, source)
    lifespan = int(cfg.get('feeds', 'lifespan'))
    assert note.expires - note.created == lifespan * 24 * 60 * 60 * 1000
Пример #30
0
def test_note_new_external_key():
    note = Notification(actor, verb_inf, note_object, source, external_key=external_key)
    assert_note_ok(note, actor=actor, verb_inf=verb_inf,
                   object=note_object, source=source, external_key=external_key)