From 663063e06c5f43fa990d6260e4bcf66f52541b7f Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Tue, 12 Mar 2024 21:41:05 -0700 Subject: [PATCH] test_integrations: add ATProto => Web follow --- protocol.py | 5 +++- tests/test_integrations.py | 58 ++++++++++++++++++++++++++++++++++++-- web.py | 10 +++++-- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/protocol.py b/protocol.py index ab5acfa..6d13222 100644 --- a/protocol.py +++ b/protocol.py @@ -1248,7 +1248,7 @@ def send_task(): logging.info(f'Sending {obj.key.id()} AS1: {json_dumps(obj.as1, indent=2)}') if (target not in obj.undelivered and target not in obj.failed - and 'force' not in request.values): + and 'force' not in request.values): logger.info(f"{url} not in {obj.key.id()} undelivered or failed, giving up") return r'¯\_(ツ)_/¯', 204 @@ -1267,6 +1267,9 @@ def send_task(): if not code and not body: logger.info(str(e), exc_info=True) + if sent is False: + logger.info(f'Failed sending {obj.key.id()} to {url}') + # write results to Object @ndb.transactional() def update_object(obj_key): diff --git a/tests/test_integrations.py b/tests/test_integrations.py index e9510af..d4282a7 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -11,6 +11,7 @@ import app from atproto import ATProto import hub from models import Target +from web import Web from .testutil import ATPROTO_KEY, TestCase from . import test_atproto @@ -19,6 +20,7 @@ from . import test_web DID_DOC = { **test_atproto.DID_DOC, 'id': 'did:plc:alice', + 'alsoKnownAs': ['at://alice.com'], } @@ -31,7 +33,7 @@ class IntegrationTests(TestCase): """ATProto poll notifications, deliver reply to ActivityPub. ActivityPub original post http://inst/post by bob - ATProto reply 123 by alice + ATProto reply 123 by alice.com (did:plc:alice) https://github.com/snarfed/bridgy-fed/issues/720 """ @@ -69,7 +71,7 @@ class IntegrationTests(TestCase): 'author': { '$type': 'app.bsky.actor.defs#profileView', 'did': 'did:plc:alice', - 'handle': 'alice', + 'handle': 'alice.com', }, 'reason': 'reply', 'record': { @@ -115,3 +117,55 @@ 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): + """ATProto poll notifications, deliver follow to Web. + + ATProto user alice.com (did:plc:alice) + ATProto follow at://did:plc:alice/app.bsky.graph.follow/123 + Web user bob.com + """ + # setup + 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='bob.com', cls=Web, + copies=[Target(uri='did:plc:bob', protocol='atproto')]) + + # ATProto listNotifications => receive + mock_get.side_effect = [ + # ATProto listNotifications + requests_response({ + 'cursor': '...', + 'notifications': [{ + 'uri': 'at://did:plc:alice/app.bsky.graph.follow/123', + 'cid': '...', + 'author': { + '$type': 'app.bsky.actor.defs#profileView', + 'did': 'did:plc:alice', + 'handle': 'alice.com', + }, + 'reason': 'follow', + 'record': { + '$type': 'app.bsky.graph.follow', + 'subject': 'did:plc:bob', + 'createdAt': '2022-01-02T03:04:05.000Z', + }, + }], + }), + # webmention discovery + test_web.WEBMENTION_REL_LINK, + ] + + resp = self.post('/queue/atproto-poll-notifs', client=hub.app.test_client()) + self.assertEqual(200, resp.status_code) + + self.assert_req(mock_get, 'https://bob.com/') + self.assert_req(mock_post, 'https://bob.com/webmention', data={ + 'source': 'https://atproto.brid.gy/convert/web/at://did:plc:alice/app.bsky.graph.follow/123', + 'target': 'https://bob.com/', + }, allow_redirects=False, headers={'Accept': '*/*'}) diff --git a/web.py b/web.py index 2276cd4..a1aed80 100644 --- a/web.py +++ b/web.py @@ -382,10 +382,16 @@ class Web(User, Protocol): if verb in ('accept', 'undo'): logger.info(f'Skipping sending {verb} (not supported in webmention/mf2) to {url}') return False - elif url not in as1.targets(obj.as1): + + targets = as1.targets(obj.as1) + if not (url in targets or + # homepage, check domain too + (urlparse(url).path.strip('/') == '' + and util.domain_from_link(url) in targets)): # logger.info(f'Skipping sending to {url} , not a target in the object') return False - elif to_cls.is_blocklisted(url): + + if to_cls.is_blocklisted(url): logger.info(f'Skipping sending to blocklisted {url}') return False