diff --git a/pages.py b/pages.py
index 46c82c8..86b4eea 100644
--- a/pages.py
+++ b/pages.py
@@ -5,7 +5,7 @@ import logging
import os
from flask import g, render_template, request
-from google.cloud import ndb
+from google.cloud.ndb import tasklets
from google.cloud.ndb.query import AND, OR
from google.cloud.ndb.stats import KindStat
from granary import as1, as2, atom, microformats2, rss
@@ -147,19 +147,24 @@ def feed(protocol, id):
.fetch(PAGE_SIZE)
activities = [obj.as1 for obj in objects if not obj.deleted]
- # fill in authors, actors, objects stored in their own Objects
+ # hydrate authors, actors, objects from stored Objects
fields = 'author', 'actor', 'object'
- hydrate_ids = [id for id in itertools.chain(
- *[[a[f] for f in fields if isinstance(a.get(f), str)]
- for a in activities])]
- if hydrate_ids:
- keys = [ndb.Key(Object, id) for id in hydrate_ids]
- hydrated = {o.key.id(): o.as1 for o in ndb.get_multi(keys) if o}
- for a in activities:
- for field in fields:
- id = a.get(field)
- if isinstance(id, str) and hydrated.get(id):
- a[field] = hydrated[id]
+ gets = []
+ for a in activities:
+ for field in fields:
+ val = as1.get_object(a, field)
+ if val and val.keys() <= set(['id']):
+ def hydrate(a, f):
+ def maybe_set(future):
+ if future.result() and future.result().as1:
+ a[f] = future.result().as1
+ return maybe_set
+
+ future = Object.get_by_id_async(val['id'])
+ future.add_done_callback(hydrate(a, field))
+ gets.append(future)
+
+ tasklets.wait_all(gets)
actor = {
'displayName': id,
diff --git a/tests/test_pages.py b/tests/test_pages.py
index 5e7fe6b..ea7f581 100644
--- a/tests/test_pages.py
+++ b/tests/test_pages.py
@@ -1,15 +1,10 @@
"""Unit tests for pages.py."""
from google.cloud import ndb
from granary import atom, microformats2, rss
-from granary.tests.test_as1 import (
- ACTOR,
- COMMENT,
- NOTE,
-)
from oauth_dropins.webutil import util
# import first so that Fake is defined before URL routes are registered
-from .testutil import Fake, TestCase
+from .testutil import Fake, TestCase, ACTOR, COMMENT, MENTION, NOTE
from activitypub import ActivityPub
from models import Object, Follower
@@ -24,11 +19,12 @@ ACTOR_WITH_PREFERRED_USERNAME = {
def contents(activities):
- return [(a.get('object') or a)['content'] for a in activities]
+ return [(a.get('object') or a)['content'].splitlines()[0]
+ for a in activities]
class PagesTest(TestCase):
- EXPECTED = contents([COMMENT, NOTE])
+ EXPECTED = contents([COMMENT, NOTE, NOTE])
def setUp(self):
super().setUp()
@@ -254,19 +250,6 @@ class PagesTest(TestCase):
def test_feed_html(self):
self.add_objects()
- # note with author in separate Object
- note_2 = {
- 'objectType': 'note',
- 'content': 'foo',
- 'author': 'fake:alice',
- }
- alice = {
- 'displayName': 'Ms Alice Macbeth',
- }
- user = ndb.Key(Web, 'user.com')
- self.store_object(id='fake:note_2', feed=[user], our_as1=note_2)
- self.store_object(id='fake:alice', our_as1=alice)
-
# repost with object (original post) in separate Object
repost = {
'objectType': 'activity',
@@ -277,15 +260,17 @@ class PagesTest(TestCase):
'objectType': 'note',
'content': 'biff',
}
- self.store_object(id='fake:repost', feed=[user], our_as1=repost)
+ self.store_object(id='fake:repost', feed=[self.user.key], our_as1=repost)
self.store_object(id='fake:orig', our_as1=orig)
got = self.client.get('/web/user.com/feed')
self.assert_equals(200, got.status_code)
- self.assert_equals(self.EXPECTED + ['foo', 'biff'],
+ self.assert_equals(['biff'] + self.EXPECTED,
contents(microformats2.html_to_activities(got.text)))
- self.assertIn('Ms Alice Macbeth', got.text)
- self.assertIn('biff', got.text)
+
+ # NOTE's and MENTION's authors; check for two instances
+ bob = 'Bob'
+ assert got.text.index(bob) != got.text.rindex(bob)
def test_feed_atom_empty(self):
got = self.client.get('/web/user.com/feed?format=atom')
@@ -298,6 +283,16 @@ class PagesTest(TestCase):
self.assert_equals(200, got.status_code)
self.assert_equals(self.EXPECTED, contents(atom.atom_to_activities(got.text)))
+ # NOTE's and MENTION's authors; check for two instances
+ bob = """
+ https://plus.google.com/bob
+
+ Bob
+"""
+ assert got.text.index(bob) != got.text.rindex(bob)
+ # COMMENT's author
+ self.assertIn('Dr. Eve', got.text)
+
def test_feed_rss_empty(self):
got = self.client.get('/web/user.com/feed?format=rss')
self.assert_equals(200, got.status_code)
@@ -309,6 +304,12 @@ class PagesTest(TestCase):
self.assert_equals(200, got.status_code)
self.assert_equals(self.EXPECTED, contents(rss.to_activities(got.text)))
+ # NOTE's and MENTION's authors; check for two instances
+ bob = '- (Bob)'
+ assert got.text.index(bob) != got.text.rindex(bob)
+ # COMMENT's author
+ self.assertIn('Dr. Eve', got.text)
+
def test_nodeinfo(self):
# just check that it doesn't crash
self.client.get('/nodeinfo.json')
diff --git a/tests/testutil.py b/tests/testutil.py
index 1b2fab4..603e4f8 100644
--- a/tests/testutil.py
+++ b/tests/testutil.py
@@ -16,6 +16,7 @@ from flask import g
from google.cloud import ndb
from granary import as2
from granary.tests.test_as1 import (
+ ACTOR,
COMMENT,
MENTION,
NOTE,
@@ -33,6 +34,25 @@ import protocol
logger = logging.getLogger(__name__)
+NOTE = {
+ **NOTE,
+ # bare string author id
+ 'author': ACTOR['id'],
+}
+MENTION = {
+ **MENTION,
+ # author object with just id
+ 'author': {'id': ACTOR['id']},
+}
+COMMENT = {
+ **COMMENT,
+ # full author object
+ 'author': {
+ **ACTOR,
+ 'displayName': 'Dr. Eve',
+ },
+}
+
class Fake(User, protocol.Protocol):
ABBREV = 'fa'
@@ -237,25 +257,36 @@ class TestCase(unittest.TestCase, testutil.Asserts):
users=[user],
notify=[user],
feed=[user],
- as2=as2.from_as1(NOTE))
- # different domain
- nope = ndb.Key(Web, 'nope.org')
+ our_as1=NOTE)
+ # post with mention
self.store_object(id='b',
- notify=[nope],
- feed=[nope],
- as2=as2.from_as1(MENTION))
+ notify=[user],
+ feed=[user],
+ our_as1=MENTION)
# reply
self.store_object(id='d',
notify=[user],
feed=[user],
- as2=as2.from_as1(COMMENT))
+ our_as1=COMMENT)
# not feed/notif
- self.store_object(id='e', users=[user], as2=as2.from_as1(NOTE))
+ self.store_object(id='e',
+ users=[user],
+ our_as1=NOTE)
# deleted
self.store_object(id='f',
notify=[user],
feed=[user],
- as2=as2.from_as1(NOTE), deleted=True)
+ our_as1=NOTE,
+ deleted=True)
+ # different domain
+ nope = ndb.Key(Web, 'nope.org')
+ self.store_object(id='g',
+ notify=[nope],
+ feed=[nope],
+ our_as1=MENTION)
+
+ # actor whose id is in NOTE.author
+ self.store_object(id=ACTOR['id'], our_as1=ACTOR)
@staticmethod
def store_object(**kwargs):