diff --git a/atproto.py b/atproto.py index efeabc3..fe81739 100644 --- a/atproto.py +++ b/atproto.py @@ -532,13 +532,15 @@ class ATProto(User, Protocol): if uri := strong_ref.get('uri'): # TODO: fail if this load fails? since we don't populate CID if ref_obj := ATProto.load(uri): + if not ref_obj.bsky.get('cid'): + ref_obj = ATProto.load(uri, remote=True) strong_ref.update({ 'cid': ref_obj.bsky.get('cid'), 'uri': ref_obj.key.id(), }) match ret.get('$type'): - case 'app.bsky.feed.like' | 'app.bsky.feed.repost': + case 'app.bsky.feed.like' | 'app.bsky.feed.repost': populate_cid(ret['subject']) case 'app.bsky.feed.post' if ret.get('reply'): populate_cid(ret['reply']['root']) diff --git a/tests/test_atproto.py b/tests/test_atproto.py index 1f606ce..1b1ee2b 100644 --- a/tests/test_atproto.py +++ b/tests/test_atproto.py @@ -493,6 +493,36 @@ class ATProtoTest(TestCase): 'object': 'at://bob.net/app.bsky.feed.post/tid', }))) + @patch('requests.get', return_value=requests_response({ # AppView getRecord + 'uri': 'at://did:plc:user/app.bsky.feed.post/tid', + 'cid': 'my sidd', + 'value': {'$type': 'app.bsky.feed.post'}, + })) + def test_convert_populate_cid_refetch_cid(self, mock_get): + # existing Object with post but missing cid + self.store_object(id='did:plc:user', raw=DID_DOC) + self.store_object(id='at://did:plc:user/app.bsky.feed.post/tid', bsky={ + '$type': 'app.bsky.feed.post', + 'cid': '', + }) + + self.assertEqual({ + '$type': 'app.bsky.feed.like', + 'subject': { + 'uri': 'at://did:plc:user/app.bsky.feed.post/tid', + 'cid': 'my sidd', + }, + 'createdAt': '2022-01-02T03:04:05.000Z', + }, ATProto.convert(Object(our_as1={ + 'objectType': 'activity', + 'verb': 'like', + 'object': 'at://did:plc:user/app.bsky.feed.post/tid', + }))) + + mock_get.assert_called_with( + 'https://api.bsky-sandbox.dev/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Auser&collection=app.bsky.feed.post&rkey=tid', + json=None, data=None, headers=ANY) + def test_convert_blobs_false(self): self.assertEqual({ '$type': 'app.bsky.actor.profile', diff --git a/tests/test_integrations.py b/tests/test_integrations.py index 1bef7ce..592fea5 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -1,4 +1,5 @@ """Integration tests.""" +import copy from unittest.mock import patch from arroba.datastore_storage import DatastoreStorage @@ -10,7 +11,7 @@ from activitypub import ActivityPub import app from atproto import ATProto from dns.resolver import NXDOMAIN -from granary.tests.test_bluesky import ACTOR_PROFILE_BSKY +from granary.tests.test_bluesky import ACTOR_PROFILE_BSKY, POST_BSKY import hub from models import Object, Target from web import Web @@ -127,6 +128,7 @@ class IntegrationTests(TestCase): 'to': ['https://www.w3.org/ns/activitystreams#Public'], }) + @patch('requests.post', return_value=requests_response('')) @patch('requests.get') def test_atproto_follow_to_web(self, mock_get, mock_post): @@ -180,6 +182,7 @@ class IntegrationTests(TestCase): 'target': 'https://bob.com/', }, allow_redirects=False, headers={'Accept': '*/*'}) + @patch('dns.resolver.resolve', side_effect=NXDOMAIN()) @patch('oauth_dropins.webutil.appengine_config.tasks_client.create_task') @patch('requests.post', side_effect=[ @@ -213,7 +216,7 @@ class IntegrationTests(TestCase): # alice profile requests_response(PROFILE_GETRECORD), ]) - def test_web_follow_to_atproto(self, mock_get, mock_post, _, __): + def test_web_follow_of_atproto(self, mock_get, mock_post, _, __): """Incoming webmention for a web follow of an ATProto bsky.app profile URL. Web user bob.com @@ -270,3 +273,82 @@ class IntegrationTests(TestCase): 'subject': 'did:plc:alice', 'createdAt': '2022-01-02T03:04:05.000Z', }], list(records['app.bsky.graph.follow'].values())) + + + @patch('oauth_dropins.webutil.appengine_config.tasks_client.create_task') + @patch('requests.get', side_effect=[ + # getRecord of original post + # alice profile + requests_response({ + 'uri': 'at://did:plc:alice/app.bsky.feed.post/123', + 'cid': 'sydd', + 'value': POST_BSKY, + }), + ]) + def test_activitypub_like_of_atproto(self, mock_get, _): + """AP inbox delivery of a Like of an ATProto bsky.app profile URL. + + ActivityPub user @bob@inst , https://inst/bob + ATProto user alice.com (did:plc:alice) + Like is https://inst/like + """ + self.store_object(id='did:plc:alice', raw=DID_DOC) + alice = self.make_user(id='did:plc:alice', cls=ATProto) + + storage = DatastoreStorage() + Repo.create(storage, 'did:plc:bob', signing_key=ATPROTO_KEY) + bob = self.make_user(id='https://inst/bob', cls=ActivityPub, + copies=[Target(uri='did:plc:bob', protocol='atproto')], + obj_as2={ + 'type': 'Person', + 'id': 'https://inst/bob', + 'name': 'Bob', + }) + + bob_did_doc = copy.deepcopy(test_atproto.DID_DOC) + bob_did_doc['service'][0]['serviceEndpoint'] = 'https://atproto.brid.gy/' + bob_did_doc.update({ + 'id': 'did:plc:bob', + 'alsoKnownAs': ['at://bob.inst.ap.brid.gy'], + }) + self.store_object(id='did:plc:bob', raw=bob_did_doc) + + # existing Object with original post, *without* cid. we should refetch. + Object(id='at://did:plc:alice/app.bsky.feed.post/123', bsky=POST_BSKY).put() + + # inbox delivery + like = { + 'type': 'Like', + 'id': 'http://inst/like', + 'actor': 'https://inst/bob', + 'object': 'https://atproto.brid.gy/convert/ap/at://did:plc:alice/app.bsky.feed.post/123', + } + resp = self.post('/ap/atproto/did:plc:alice/inbox', json=like) + self.assertEqual(202, resp.status_code) + + # check results + self.assertEqual({ + **like, + # TODO: stop normalizing this in the original protocol's data + 'object': 'at://did:plc:alice/app.bsky.feed.post/123', + }, Object.get_by_id('http://inst/like').as2) + + repo = storage.load_repo('did:plc:bob') + + records = repo.get_contents() + self.assertEqual(['app.bsky.feed.like'], list(records.keys())) + self.assertEqual([{ + '$type': 'app.bsky.feed.like', + 'subject': { + 'uri': 'at://did:plc:alice/app.bsky.feed.post/123', + 'cid': 'sydd', + }, + 'createdAt': '2022-01-02T03:04:05.000Z', + }], list(records['app.bsky.feed.like'].values())) + + # we needed to refetch the original post + self.assert_object(id='at://did:plc:alice/app.bsky.feed.post/123', + source_protocol='atproto', bsky={ + **POST_BSKY, + 'cid': 'sydd', + })