diff --git a/activitypub.py b/activitypub.py index 5a008df..9e0c36a 100644 --- a/activitypub.py +++ b/activitypub.py @@ -194,7 +194,7 @@ class ActivityPub(User, Protocol): return actor.get('publicInbox') or actor.get('inbox') @classmethod - def send(to_cls, obj, url, orig_obj=None, log_data=True): + def send(to_cls, obj, url, orig_obj=None): """Delivers an activity to an inbox URL. If ``obj.recipient_obj`` is set, it's interpreted as the receiving actor @@ -208,7 +208,7 @@ class ActivityPub(User, Protocol): if not activity.get('actor'): logger.warning('Outgoing AP activity has no actor!') - return signed_post(url, log_data=log_data, data=activity).ok + return signed_post(url, data=activity).ok @classmethod def fetch(cls, obj, **kwargs): @@ -447,7 +447,7 @@ def signed_post(url, **kwargs): return signed_request(util.requests_post, url, **kwargs) -def signed_request(fn, url, data=None, log_data=True, headers=None, **kwargs): +def signed_request(fn, url, data=None, headers=None, **kwargs): """Wraps ``requests.*`` and adds HTTP Signature. If the current session has a user (ie in ``g.user``), signs with that user's @@ -457,7 +457,6 @@ def signed_request(fn, url, data=None, log_data=True, headers=None, **kwargs): fn (callable): :func:`util.requests_get` or :func:`util.requests_post` url (str): data (dict): optional AS2 object - log_data (bool): whether to log full data object kwargs: passed through to requests Returns: @@ -473,8 +472,7 @@ def signed_request(fn, url, data=None, log_data=True, headers=None, **kwargs): user = default_signature_user() if data: - if log_data: - logger.info(f'Sending AS2 object: {json_dumps(data, indent=2)}') + logger.info(f'Sending AS2 object: {json_dumps(data, indent=2)}') data = json_dumps(data).encode() headers = { @@ -511,7 +509,7 @@ def signed_request(fn, url, data=None, log_data=True, headers=None, **kwargs): if resp.is_redirect and fn == util.requests_get: new_url = urljoin(url, resp.headers['Location']) return signed_request(fn, new_url, data=data, headers=headers, - log_data=log_data, **kwargs) + **kwargs) type = common.content_type(resp) if (type and type != 'text/html' and diff --git a/atproto.py b/atproto.py index 3bb7bb1..16d76eb 100644 --- a/atproto.py +++ b/atproto.py @@ -257,7 +257,7 @@ class ATProto(User, Protocol): user.put() @classmethod - def send(to_cls, obj, url, orig_obj=None, log_data=True): + def send(to_cls, obj, url, orig_obj=None): """Creates a record if we own its repo. Creates the repo first if it doesn't exist. @@ -311,11 +311,8 @@ class ATProto(User, Protocol): ndb.transactional() def write(): tid = next_tid() - log_msg = f'Storing ATProto app.bsky.feed.post {tid}' - if log_data: - log_msg += ': ' + json_dumps(dag_json.encode(record).decode(), - indent=2) - logger.info(log_msg) + logger.info(f'Storing ATProto app.bsky.feed.post {tid}: ' + + json_dumps(dag_json.encode(record).decode(), indent=2)) repo.apply_writes( [Write(action=Action.CREATE, collection='app.bsky.feed.post', diff --git a/common.py b/common.py index 8591b94..18ca328 100644 --- a/common.py +++ b/common.py @@ -14,6 +14,7 @@ from oauth_dropins.webutil import util, webmention from oauth_dropins.webutil.appengine_config import tasks_client from oauth_dropins.webutil import appengine_info from oauth_dropins.webutil.appengine_info import DEBUG +from oauth_dropins.webutil import flask_util logger = logging.getLogger(__name__) @@ -73,6 +74,7 @@ USER_AGENT = 'Bridgy Fed (https://fed.brid.gy/)' util.set_user_agent(USER_AGENT) TASKS_LOCATION = 'us-central1' +RUN_TASKS_INLINE = False # overridden by unit tests def base64_to_long(x): @@ -264,7 +266,7 @@ def create_task(queue, **params): assert queue path = f'/queue/{queue}' - if appengine_info.LOCAL_SERVER: + if RUN_TASKS_INLINE or appengine_info.LOCAL_SERVER: logger.info(f'Running task inline: {queue} {params}') from flask_app import app return app.test_client().post( @@ -283,7 +285,7 @@ def create_task(queue, **params): 'app_engine_http_request': { 'http_method': 'POST', 'relative_uri': path, - 'body': urllib.parse.urlencode(params).encode(), + 'body': urllib.parse.urlencode(sorted(params.items())).encode(), 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, }, }) diff --git a/protocol.py b/protocol.py index b921530..1668c31 100644 --- a/protocol.py +++ b/protocol.py @@ -1,16 +1,14 @@ """Base protocol class and common code.""" -from concurrent.futures import ThreadPoolExecutor import copy import logging import threading from urllib.parse import urljoin from cachetools import LRUCache -from flask import copy_current_request_context, g, request +from flask import g, request from google.cloud import ndb from google.cloud.ndb import OR from granary import as1 -from oauth_dropins.webutil.appengine_config import ndb_client from oauth_dropins.webutil.flask_util import cloud_tasks_only from oauth_dropins.webutil import util from oauth_dropins.webutil.util import json_dumps, json_loads @@ -40,8 +38,6 @@ SUPPORTED_TYPES = ( 'video', ) -DELIVER_THREADS = 10 - # activity ids that we've already handled and can now ignore. # used in Protocol.receive seen_ids = LRUCache(100000) @@ -364,7 +360,7 @@ class Protocol: return g.user.key @classmethod - def send(to_cls, obj, url, orig_obj=None, log_data=True): + def send(to_cls, obj, url, orig_obj=None): """Sends an outgoing activity. To be implemented by subclasses. @@ -374,7 +370,6 @@ class Protocol: url (str): destination URL to send to orig_obj (models.Object): the "original object" that this object refers to, eg replies to or reposts or likes - log_data (bool): whether to log full data object Returns: bool: True if the activity is sent successfully, False if it is @@ -895,7 +890,7 @@ class Protocol: if not targets: obj.status = 'ignored' obj.put() - error('No targets', status=204) + error('No targets, nothing to do ¯\_(ツ)_/¯', status=204) sorted_targets = sorted(targets.items(), key=lambda t: t[0].uri) obj.populate( @@ -904,71 +899,18 @@ class Protocol: failed=[], undelivered=[t for t, _ in sorted_targets], ) + obj.put() logger.info(f'Delivering to: {obj.undelivered}') - errors = [] # stores (target URL, code, body) tuples + # enqueue send task for each targets + for i, (target, orig_obj) in enumerate(sorted_targets): + orig_obj = orig_obj.key.urlsafe() if orig_obj else '' + user = g.user.key.urlsafe() if g.user else '' + common.create_task(queue='send', obj=obj.key.urlsafe(), + url=target.uri, protocol=target.protocol, + orig_obj=orig_obj, user=user) - # deliver to all targets, in parallel, with a thread pool - with ThreadPoolExecutor(max_workers=DELIVER_THREADS, - thread_name_prefix='deliver') as executor: - results = [] - log_data = True - - for target, orig_obj in sorted_targets: - @copy_current_request_context - def deliver_one(target, orig_obj, g_user, log_data): - """Runs on a separate thread! - - Note that this has to be defined *inside* the loop, once per - target, since @copy_current_request_context copies when the - function is defined, and we need a fresh copy of the request - context for each call/thread. - - https://www.kingname.info/2023/01/14/nested-thread-in-flask/ - """ - assert target.uri - protocol = PROTOCOLS[target.protocol] - g.user = g_user - - with ndb_client.context(): - try: - sent = protocol.send(obj, target.uri, orig_obj=orig_obj, - log_data=log_data) - if sent: - obj.add('delivered', target) - obj.remove('undelivered', target) - except BaseException as e: - code, body = util.interpret_http_exception(e) - if not code and not body: - raise - obj.add('failed', target) - obj.remove('undelivered', target) - errors.append((target.uri, code, body)) - - obj.put() - - results.append(executor.submit(deliver_one, target, orig_obj, - g.user, log_data)) - log_data = False - - # re-raise any exception that were raised - for r in results: - r.result() - - # Pass the response status code and body through as our response - if obj.delivered: - ret = 'OK' - obj.status = 'complete' - elif errors: - ret = f'Delivery failed: {errors}', 502 - obj.status = 'failed' - else: - ret = r'Nothing to do ¯\_(ツ)_/¯', 204 - obj.status = 'ignored' - - obj.put() - logger.info(f'Returning {ret}') - return ret + return 'OK', 202 @classmethod def targets(cls, obj): @@ -1203,9 +1145,9 @@ def receive_task(): Calls :meth:`Protocol.receive` with the form parameters. Parameters: - obj (google.cloud.ndb.key.Key): :class:`models.Object` to handle - user (google.cloud.ndb.key.Key): :class:`models.User` this activity is on - behalf of. This user will be loaded into ``g.user`` + obj (url-safe google.cloud.ndb.key.Key): :class:`models.Object` to handle + user (url-safe google.cloud.ndb.key.Key): :class:`models.User` this + activity is on behalf of. This user will be loaded into ``g.user`` authed_as (str): passed to :meth:`Protocol.receive` TODO: migrate incoming webmentions and AP inbox deliveries to this. The @@ -1231,3 +1173,73 @@ def receive_task(): except ValueError as e: logger.warning(e, exc_info=True) error(e, status=304) + + +@app.post('/queue/send') +@cloud_tasks_only +def send_task(): + """Task handler for sending an activity to a single specific destination. + + Calls :meth:`Protocol.send` with the form parameters. + + Parameters: + protocol (str): :class:`Protocol` to send to + url (str): destination URL to send to + obj (url-safe google.cloud.ndb.key.Key): :class:`models.Object` to send + orig_obj (url-safe google.cloud.ndb.key.Key): optional "original object" + :class:`models.Object` that this object refers to, eg replies to or + reposts or likes + user (url-safe google.cloud.ndb.key.Key): :class:`models.User` this + activity is on behalf of. This user will be loaded into ``g.user`` + """ + form = request.form.to_dict() + logger.info(f'Params: {list(form.items())}') + + # prepare + url = form['url'] + protocol = form['protocol'] + target = Target(uri=url, protocol=protocol) + + obj = ndb.Key(urlsafe=form['obj']).get() + if target not in obj.undelivered and target not in obj.failed: + logger.info(f"{url} not in {obj.key.id()} undelivered or failed, giving up") + return '¯\_(ツ)_/¯', 204 + + if user_key := form.get('user'): + g.user = ndb.Key(urlsafe=user_key).get() + orig_obj = (ndb.Key(urlsafe=form['orig_obj']).get() + if form.get('orig_obj') else None) + + # send + sent = None + try: + sent = PROTOCOLS[protocol].send(obj, url, orig_obj=orig_obj) + except BaseException as e: + code, body = util.interpret_http_exception(e) + if not code and not body: + logger.info(str(e), exc_info=True) + + # write results to Object + @ndb.transactional() + def update_object(obj_key): + obj = obj_key.get() + if target in obj.undelivered: + obj.remove('undelivered', target) + + if sent is None: + obj.add('failed', target) + else: + if target in obj.failed: + obj.remove('failed', target) + if sent: + obj.add('delivered', target) + + if not obj.undelivered: + obj.status = ('complete' if obj.delivered + else 'failed' if obj.failed + else 'ignored') + obj.put() + + update_object(obj.key) + + return '', 200 if sent else 304 diff --git a/queue.yaml b/queue.yaml index 1acf94f..6fc86c5 100644 --- a/queue.yaml +++ b/queue.yaml @@ -27,3 +27,12 @@ queue: task_retry_limit: 3 min_backoff_seconds: 120 max_doublings: 3 + +- name: send + target: default + rate: 5/s + max_concurrent_requests: 5 + retry_parameters: + task_retry_limit: 3 + min_backoff_seconds: 120 + max_doublings: 3 diff --git a/tests/test_activitypub.py b/tests/test_activitypub.py index 0078509..b49655e 100644 --- a/tests/test_activitypub.py +++ b/tests/test_activitypub.py @@ -525,7 +525,7 @@ class ActivityPubTest(TestCase): mock_post.return_value = requests_response() got = self.post('/ap/web/user.com/inbox', json=reply) - self.assertEqual(200, got.status_code, got.get_data(as_text=True)) + self.assertEqual(202, got.status_code, got.get_data(as_text=True)) self.assert_req(mock_get, 'https://user.com/post') convert_id = reply['id'].replace('://', ':/') @@ -555,11 +555,9 @@ class ActivityPubTest(TestCase): } got = self.post('/ap/fake:user/inbox', json=reply, base_url='https://fa.brid.gy/') - self.assertEqual(200, got.status_code) - - [(obj, target)] = Fake.sent - self.assertEqual('fake:my-reply#bridgy-fed-create', obj.our_as1['id']) - self.assertEqual('fake:post:target', target) + self.assertEqual(202, got.status_code) + self.assertEqual([('fake:my-reply#bridgy-fed-create', 'fake:post:target')], + Fake.sent) def test_inbox_reply_to_self_domain(self, *mocks): self._test_inbox_ignore_reply_to('http://localhost/mas.to', *mocks) @@ -610,7 +608,7 @@ class ActivityPubTest(TestCase): mock_post.return_value = requests_response() got = self.post(path, json=NOTE) - self.assertEqual(200, got.status_code, got.get_data(as_text=True)) + self.assertEqual(202, got.status_code, got.get_data(as_text=True)) expected_obj = { **as2.to_as1(NOTE_OBJECT), @@ -659,7 +657,7 @@ class ActivityPubTest(TestCase): repost = copy.deepcopy(REPOST_FULL) repost['object'] = f'http://localhost/r/{orig_url}' got = self.post('/user.com/inbox', json=repost) - self.assertEqual(200, got.status_code, got.get_data(as_text=True)) + self.assertEqual(202, got.status_code, got.get_data(as_text=True)) convert_id = REPOST['id'].replace('://', ':/') self.assert_req( @@ -705,7 +703,7 @@ class ActivityPubTest(TestCase): ] got = self.post('/ap/sharedInbox', json=REPOST) - self.assertEqual(200, got.status_code, got.get_data(as_text=True)) + self.assertEqual(202, got.status_code, got.get_data(as_text=True)) mock_post.assert_not_called() # no webmention @@ -735,7 +733,7 @@ class ActivityPubTest(TestCase): **LIKE, 'object': 'http://nope.com/post', }) - self.assertEqual(204, got.status_code) + self.assertEqual(202, got.status_code) self.assert_object('http://mas.to/like#ok', # no nope.com Web user key since it didn't exist @@ -778,7 +776,7 @@ class ActivityPubTest(TestCase): mock_post.return_value = requests_response() got = self.post('/user.com/inbox', json=LIKE) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) self.assertIn(self.as2_req('https://mas.to/actor'), mock_get.mock_calls) self.assertIn(self.req('https://user.com/post'), mock_get.mock_calls) @@ -820,7 +818,7 @@ class ActivityPubTest(TestCase): self.assertEqual(400, got.status_code) def test_inbox_follow_accept_with_id(self, *mocks): - self._test_inbox_follow_accept(FOLLOW_WRAPPED, ACCEPT, 200, *mocks) + self._test_inbox_follow_accept(FOLLOW_WRAPPED, ACCEPT, *mocks) follow = { **FOLLOW_WITH_ACTOR, @@ -846,7 +844,7 @@ class ActivityPubTest(TestCase): } accept = copy.deepcopy(ACCEPT) accept['object']['url'] = 'https://mas.to/users/swentel#followed-https://user.com/' - self._test_inbox_follow_accept(follow, accept, 200, *mocks) + self._test_inbox_follow_accept(follow, accept, *mocks) follow.update({ 'actor': ACTOR, @@ -863,7 +861,7 @@ class ActivityPubTest(TestCase): object_ids=[FOLLOW['object']]) def test_inbox_follow_accept_shared_inbox(self, *mocks): - self._test_inbox_follow_accept(FOLLOW_WRAPPED, ACCEPT, 200, *mocks, + self._test_inbox_follow_accept(FOLLOW_WRAPPED, ACCEPT, *mocks, inbox_path='/ap/sharedInbox') url = 'https://mas.to/users/swentel#followed-user.com' @@ -883,7 +881,7 @@ class ActivityPubTest(TestCase): requests_response(), # AP Accept requests.ConnectionError(), # webmention ] - self._test_inbox_follow_accept(FOLLOW_WRAPPED, ACCEPT, 502, + self._test_inbox_follow_accept(FOLLOW_WRAPPED, ACCEPT, mock_head, mock_get, mock_post) url = 'https://mas.to/users/swentel#followed-user.com' @@ -898,9 +896,8 @@ class ActivityPubTest(TestCase): type='follow', object_ids=[FOLLOW['object']]) - def _test_inbox_follow_accept(self, follow_as2, accept_as2, expected_status, - mock_head, mock_get, mock_post, - inbox_path='/user.com/inbox'): + def _test_inbox_follow_accept(self, follow_as2, accept_as2, mock_head, + mock_get, mock_post, inbox_path='/user.com/inbox'): # this should makes us make the follower ActivityPub as direct=True self.user.direct = False self.user.put() @@ -915,7 +912,7 @@ class ActivityPubTest(TestCase): mock_post.return_value = requests_response() got = self.post(inbox_path, json=follow_as2) - self.assertEqual(expected_status, got.status_code) + self.assertEqual(202, got.status_code) mock_get.assert_has_calls(( self.as2_req(FOLLOW['actor']), @@ -962,7 +959,7 @@ class ActivityPubTest(TestCase): mock_post.return_value = requests_response() got = self.post('/user.com/inbox', json=FOLLOW_WRAPPED) - self.assertEqual(204, got.status_code) + self.assertEqual(202, got.status_code) follower = Follower.query().get() self.assert_entities_equal( @@ -994,7 +991,7 @@ class ActivityPubTest(TestCase): mock_post.return_value = requests_response() got = self.post('/user.com/inbox', json=UNDO_FOLLOW_WRAPPED) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) # check that the Follower is now inactive self.assertEqual('inactive', follower.key.get().status) @@ -1014,7 +1011,7 @@ class ActivityPubTest(TestCase): mock_post.return_value = requests_response() got = self.post('/user.com/inbox', json=FOLLOW_WRAPPED) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) # check that the Follower is now active self.assertEqual('active', follower.key.get().status) @@ -1028,7 +1025,7 @@ class ActivityPubTest(TestCase): mock_post.return_value = requests_response() got = self.post('/user.com/inbox', json=UNDO_FOLLOW_WRAPPED) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) def test_inbox_undo_follow_inactive(self, mock_head, mock_get, mock_post): mock_head.return_value = requests_response(url='https://user.com/') @@ -1043,7 +1040,7 @@ class ActivityPubTest(TestCase): status='inactive') got = self.post('/user.com/inbox', json=UNDO_FOLLOW_WRAPPED) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) self.assertEqual('inactive', follower.key.get().status) def test_inbox_undo_follow_composite_object(self, mock_head, mock_get, mock_post): @@ -1061,7 +1058,7 @@ class ActivityPubTest(TestCase): undo_follow = copy.deepcopy(UNDO_FOLLOW_WRAPPED) undo_follow['object']['object'] = {'id': undo_follow['object']['object']} got = self.post('/user.com/inbox', json=undo_follow) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) self.assertEqual('inactive', follower.key.get().status) def test_inbox_unsupported_type(self, *_): @@ -1307,7 +1304,7 @@ class ActivityPubTest(TestCase): ] got = self.post('/user.com/inbox', json=LIKE) - self.assertEqual(502, got.status_code) + self.assertEqual(202, got.status_code) def test_inbox_no_webmention_endpoint(self, mock_head, mock_get, mock_post): mock_get.side_effect = [ @@ -1321,7 +1318,7 @@ class ActivityPubTest(TestCase): ] got = self.post('/user.com/inbox', json=LIKE) - self.assertEqual(204, got.status_code) + self.assertEqual(202, got.status_code) self.assert_object('http://mas.to/like#ok', notify=[self.user.key], diff --git a/tests/test_atproto.py b/tests/test_atproto.py index 1547112..704097e 100644 --- a/tests/test_atproto.py +++ b/tests/test_atproto.py @@ -58,6 +58,7 @@ class ATProtoTest(TestCase): def setUp(self): super().setUp() self.storage = DatastoreStorage() + common.RUN_TASKS_INLINE = False @patch('requests.get', return_value=requests_response(DID_DOC)) def test_put_validates_id(self, mock_get): diff --git a/tests/test_models.py b/tests/test_models.py index f00a30e..14d8262 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -21,6 +21,7 @@ from oauth_dropins.webutil import util from .testutil import Fake, OtherFake, TestCase from atproto import ATProto +import common import models from models import Follower, Object, OBJECT_EXPIRE_AGE, Target, User import protocol @@ -58,13 +59,14 @@ class UserTest(TestCase): @patch('requests.post', return_value=requests_response('OK')) # create DID on PLC def test_get_or_create_propagate(self, mock_post, mock_create_task): + common.RUN_TASKS_INLINE = False + Fake.fetchable = { 'fake:user': { **ACTOR_AS, 'image': None, # don't try to fetch as blob }, } - user = Fake.get_or_create('fake:user', propagate=True) # check that profile was fetched remotely diff --git a/tests/test_pages.py b/tests/test_pages.py index a994354..8bc6f7b 100644 --- a/tests/test_pages.py +++ b/tests/test_pages.py @@ -14,6 +14,7 @@ from oauth_dropins.webutil.testutil import requests_response from .testutil import Fake, TestCase, ACTOR, COMMENT, MENTION, NOTE from activitypub import ActivityPub +import common from models import Object, Follower, Target from web import Web @@ -386,6 +387,8 @@ class PagesTest(TestCase): @patch('requests.post', return_value=requests_response('OK')) # create DID on PLC def test_bridge_user(self, mock_post, mock_create_task): + common.RUN_TASKS_INLINE = False + Fake.fetchable = { 'fake:user': { **ACTOR_AS, diff --git a/tests/test_protocol.py b/tests/test_protocol.py index e7694cf..e966367 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -8,6 +8,7 @@ from arroba.tests.testutil import dns_answer from flask import g from google.cloud import ndb from granary import as2 +from oauth_dropins.webutil import appengine_info from oauth_dropins.webutil.flask_util import CLOUD_TASKS_QUEUE_HEADER, NoContent from oauth_dropins.webutil.testutil import requests_response import requests @@ -18,6 +19,7 @@ from .testutil import Fake, OtherFake, TestCase from activitypub import ActivityPub from app import app from atproto import ATProto +import common from models import Follower, Object, PROTOCOLS, Target, User import protocol from protocol import Protocol @@ -522,7 +524,7 @@ class ProtocolReceiveTest(TestCase): 'actor': 'fake:user', 'object': post_as1, } - self.assertEqual('OK', Fake.receive_as1(create_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(create_as1)) self.assert_object('fake:post', our_as1=post_as1, @@ -538,7 +540,7 @@ class ProtocolReceiveTest(TestCase): notify=[], ) - self.assertEqual([(obj, 'shared:target')], Fake.sent) + self.assertEqual([(obj.key.id(), 'shared:target')], Fake.sent) def test_create_post_bare_object(self): self.make_followers() @@ -548,7 +550,7 @@ class ProtocolReceiveTest(TestCase): 'objectType': 'note', 'author': 'fake:user', } - self.assertEqual('OK', Fake.receive_as1(post_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(post_as1)) self.assert_object('fake:post', our_as1=post_as1, @@ -572,7 +574,7 @@ class ProtocolReceiveTest(TestCase): notify=[], ) - self.assertEqual([(obj, 'shared:target')], Fake.sent) + self.assertEqual([(obj.key.id(), 'shared:target')], Fake.sent) def test_create_post_bare_object_existing_failed_create(self): self.make_followers() @@ -585,7 +587,7 @@ class ProtocolReceiveTest(TestCase): self.store_object(id='fake:post', our_as1=post_as1) self.store_object(id='fake:post#bridgy-fed-create', status='failed') - self.assertEqual('OK', Fake.receive_as1(post_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(post_as1)) obj = self.assert_object('fake:post#bridgy-fed-create', status='complete', @@ -595,7 +597,7 @@ class ProtocolReceiveTest(TestCase): ignore=['our_as1'], ) - self.assertEqual([(obj, 'shared:target')], Fake.sent) + self.assertEqual([(obj.key.id(), 'shared:target')], Fake.sent) def test_create_post_bare_object_no_existing_create(self): self.make_followers() @@ -607,7 +609,7 @@ class ProtocolReceiveTest(TestCase): } self.store_object(id='fake:post', our_as1=post_as1) - self.assertEqual('OK', Fake.receive_as1(post_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(post_as1)) obj = self.assert_object('fake:post#bridgy-fed-create', status='complete', @@ -617,7 +619,7 @@ class ProtocolReceiveTest(TestCase): ignore=['our_as1'], ) - self.assertEqual([(obj, 'shared:target')], Fake.sent) + self.assertEqual([(obj.key.id(), 'shared:target')], Fake.sent) def test_create_post_use_instead(self): self.make_user('fake:instead', cls=Fake, use_instead=self.user.key, obj_mf2={ @@ -636,7 +638,7 @@ class ProtocolReceiveTest(TestCase): } obj = self.store_object(id='fake:post', our_as1=post_as1) - self.assertEqual('OK', Fake.receive_as1(post_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(post_as1)) self.assertEqual(1, len(Fake.sent)) self.assertEqual('shared:target', Fake.sent[0][1]) @@ -677,7 +679,7 @@ class ProtocolReceiveTest(TestCase): 'actor': 'fake:user', 'object': post_as1, } - self.assertEqual('OK', Fake.receive_as1(update_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(update_as1)) self.assert_object('fake:post', our_as1=post_as1, @@ -693,7 +695,7 @@ class ProtocolReceiveTest(TestCase): notify=[], ) - self.assertEqual([(obj, 'shared:target')], Fake.sent) + self.assertEqual([(obj.key.id(), 'shared:target')], Fake.sent) def test_update_post_bare_object(self): self.make_followers() @@ -756,7 +758,7 @@ class ProtocolReceiveTest(TestCase): 'actor': 'fake:user', 'object': reply_as1, } - self.assertEqual('OK', Fake.receive_as1(create_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(create_as1)) self.assert_object('fake:reply', our_as1=reply_as1, @@ -771,7 +773,7 @@ class ProtocolReceiveTest(TestCase): notify=[self.bob.key], ) - self.assertEqual([(obj, 'fake:post:target')], Fake.sent) + self.assertEqual([(obj.key.id(), 'fake:post:target')], Fake.sent) def test_create_reply_bare_object(self): self.make_followers() @@ -787,7 +789,7 @@ class ProtocolReceiveTest(TestCase): 'id': 'fake:post', 'author': 'fake:bob', } - self.assertEqual('OK', Fake.receive_as1(reply_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(reply_as1)) self.assert_object('fake:reply', our_as1=reply_as1, @@ -811,7 +813,7 @@ class ProtocolReceiveTest(TestCase): notify=[self.bob.key], ) - self.assertEqual([(obj, 'fake:post:target')], Fake.sent) + self.assertEqual([(obj.key.id(), 'fake:post:target')], Fake.sent) def test_create_reply_to_self_delivers_to_followers(self): self.make_followers() @@ -829,18 +831,18 @@ class ProtocolReceiveTest(TestCase): 'id': 'fake:post', 'author': 'fake:user', } - self.assertEqual('OK', Fake.receive_as1(reply_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(reply_as1)) self.assert_object('fake:reply', our_as1=reply_as1, type='note', feed=[self.alice.key, self.bob.key, eve.key]) obj = Object.get_by_id(id='fake:reply#bridgy-fed-create') self.assertEqual([ - (obj, 'fake:post:target'), - (obj, 'shared:target'), + (obj.key.id(), 'fake:post:target'), + (obj.key.id(), 'shared:target'), ], Fake.sent) self.assertEqual([ - (obj, 'other:eve:target'), + (obj.key.id(), 'other:eve:target'), ], OtherFake.sent) def test_update_reply(self): @@ -865,7 +867,7 @@ class ProtocolReceiveTest(TestCase): 'actor': 'fake:user', 'object': reply_as1, } - self.assertEqual('OK', Fake.receive_as1(update_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(update_as1)) self.assert_object('fake:reply', our_as1=reply_as1, @@ -879,7 +881,7 @@ class ProtocolReceiveTest(TestCase): users=[self.user.key, self.alice.key], notify=[self.bob.key], ) - self.assertEqual([(obj, 'fake:post:target')], Fake.sent) + self.assertEqual([(obj.key.id(), 'fake:post:target')], Fake.sent) def test_repost(self): self.make_followers() @@ -895,7 +897,7 @@ class ProtocolReceiveTest(TestCase): 'actor': 'fake:user', 'object': 'fake:post', } - self.assertEqual('OK', Fake.receive_as1(repost_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(repost_as1)) obj = self.assert_object('fake:repost', status='complete', @@ -914,8 +916,8 @@ class ProtocolReceiveTest(TestCase): feed=[self.alice.key, self.bob.key], ) self.assertEqual([ - (obj, 'fake:post:target'), - (obj, 'shared:target'), + (obj.key.id(), 'fake:post:target'), + (obj.key.id(), 'shared:target'), ], Fake.sent) def test_repost_twitter_blocklisted(self): @@ -952,7 +954,7 @@ class ProtocolReceiveTest(TestCase): 'actor': 'fake:user', 'object': 'fake:post', } - self.assertEqual('OK', Fake.receive_as1(like_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(like_as1)) like_obj = self.assert_object('fake:like', users=[self.user.key], @@ -963,7 +965,7 @@ class ProtocolReceiveTest(TestCase): type='like', object_ids=['fake:post']) - self.assertEqual([(like_obj, 'fake:post:target')], Fake.sent) + self.assertEqual([(like_obj.key.id(), 'fake:post:target')], Fake.sent) def test_like_no_object_error(self): with self.assertRaises(BadRequest): @@ -1003,7 +1005,7 @@ class ProtocolReceiveTest(TestCase): 'actor': 'fake:user', 'object': 'fake:post', } - self.assertEqual('OK', Fake.receive_as1(delete_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(delete_as1)) self.assert_object('fake:post', our_as1=post_as1, @@ -1020,7 +1022,7 @@ class ProtocolReceiveTest(TestCase): users=[self.user.key], notify=[], ) - self.assertEqual([(obj, 'shared:target')], Fake.sent) + self.assertEqual([(obj.key.id(), 'shared:target')], Fake.sent) def test_delete_no_followers_no_stored_object(self): delete_as1 = { @@ -1097,7 +1099,7 @@ class ProtocolReceiveTest(TestCase): } sent = [] - def send(obj, url, orig_obj=None, log_data=True): + def send(obj, url, orig_obj=None): self.assertEqual(create_as1, obj.as1) if not sent: self.assertEqual('target:1', url) @@ -1110,7 +1112,7 @@ class ProtocolReceiveTest(TestCase): mock_send.side_effect = send - self.assertEqual('OK', Fake.receive_as1(create_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(create_as1)) self.assert_object('fake:post', our_as1=post_as1, @@ -1167,7 +1169,7 @@ class ProtocolReceiveTest(TestCase): object_ids=['fake:user'], ) - self.assertEqual([(update_obj, 'shared:target')], Fake.sent) + self.assertEqual([(update_obj.key.id(), 'shared:target')], Fake.sent) def test_mention_object(self, *mocks): self.alice.obj.our_as1 = {'id': 'fake:alice', 'objectType': 'person'} @@ -1188,7 +1190,7 @@ class ProtocolReceiveTest(TestCase): 'url': 'fake:bob', }], } - self.assertEqual('OK', Fake.receive_as1(mention_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(mention_as1)) self.assert_object('fake:mention', our_as1=mention_as1, @@ -1211,8 +1213,10 @@ class ProtocolReceiveTest(TestCase): notify=[self.alice.key, self.bob.key], ) - self.assertEqual([(obj, 'fake:alice:target'), (obj, 'fake:bob:target')], - Fake.sent) + self.assertEqual([ + (obj.key.id(), 'fake:alice:target'), + (obj.key.id(), 'fake:bob:target'), + ], Fake.sent) def test_follow(self): self._test_follow() @@ -1237,7 +1241,7 @@ class ProtocolReceiveTest(TestCase): 'object': 'fake:user', **extra, } - self.assertEqual('OK', Fake.receive_as1(follow_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(follow_as1)) user = Fake.get_by_id('fake:user') follow_obj = self.assert_object('fake:follow', @@ -1269,8 +1273,8 @@ class ProtocolReceiveTest(TestCase): ) self.assertEqual([ - (accept_obj, 'fake:alice:target'), - (follow_obj, 'fake:user:target'), + (accept_obj.key.id(), 'fake:alice:target'), + (follow_obj.key.id(), 'fake:user:target'), ], Fake.sent) self.assert_entities_equal( @@ -1330,7 +1334,7 @@ class ProtocolReceiveTest(TestCase): 'actor': 'fake:alice', 'object': 'fake:user', } - self.assertEqual('OK', Fake.receive_as1(stop_as1)) + self.assertEqual(('OK', 202), Fake.receive_as1(stop_as1)) stop_obj = self.assert_object('fake:stop-following', our_as1=stop_as1, @@ -1343,13 +1347,13 @@ class ProtocolReceiveTest(TestCase): ) self.assertEqual('inactive', follower.key.get().status) - self.assertEqual([(stop_obj, 'fake:user:target')], Fake.sent) + self.assertEqual([(stop_obj.key.id(), 'fake:user:target')], Fake.sent) def test_stop_following_doesnt_exist(self): self.user.obj.our_as1 = {'id': 'fake:user'} self.user.obj.put() - self.assertEqual('OK', Fake.receive_as1({ + self.assertEqual(('OK', 202), Fake.receive_as1({ 'id': 'fake:stop-following', 'objectType': 'activity', 'verb': 'stop-following', @@ -1358,11 +1362,7 @@ class ProtocolReceiveTest(TestCase): })) self.assertEqual(0, Follower.query().count()) - - self.assertEqual(1, len(Fake.sent)) - obj, target = Fake.sent[0] - self.assertEqual('fake:stop-following', obj.key.id()) - self.assertEqual('fake:user:target', target) + self.assertEqual([('fake:stop-following', 'fake:user:target')], Fake.sent) def test_stop_following_inactive(self): follower = Follower.get_or_create(to=self.user, from_=self.alice, @@ -1371,7 +1371,7 @@ class ProtocolReceiveTest(TestCase): self.user.obj.our_as1 = {'id': 'fake:user'} self.user.obj.put() - self.assertEqual('OK', Fake.receive_as1({ + self.assertEqual(('OK', 202), Fake.receive_as1({ 'id': 'fake:stop-following', 'objectType': 'activity', 'verb': 'stop-following', @@ -1380,11 +1380,7 @@ class ProtocolReceiveTest(TestCase): })) self.assertEqual('inactive', follower.key.get().status) - - self.assertEqual(1, len(Fake.sent)) - obj, target = Fake.sent[0] - self.assertEqual('fake:stop-following', obj.key.id()) - self.assertEqual('fake:user:target', target) + self.assertEqual([('fake:stop-following', 'fake:user:target')], Fake.sent) @skip def test_receive_from_bridgy_fed_domain_fails(self): @@ -1420,13 +1416,15 @@ class ProtocolReceiveTest(TestCase): 'object': ['other:dan', 'fake:alice'], } - self.assertEqual('OK', OtherFake.receive_as1(follow_as1)) + self.assertEqual(('OK', 202), OtherFake.receive_as1(follow_as1)) self.assertEqual(1, len(OtherFake.sent)) - self.assertEqual('accept', OtherFake.sent[0][0].type) + self.assertEqual( + 'https://fa.brid.gy/ap/fake:alice/followers#accept-http://x.com/follow', + OtherFake.sent[0][0]) self.assertEqual(1, len(Fake.sent)) - self.assertEqual('follow', Fake.sent[0][0].type) + self.assertEqual('http://x.com/follow', Fake.sent[0][0]) followers = Follower.query().fetch() self.assertEqual(1, len(followers)) @@ -1450,13 +1448,12 @@ class ProtocolReceiveTest(TestCase): with self.assertRaises(NoContent): Fake.receive_as1(follow_as1) - (bob_obj, bob_target), (eve_obj, eve_target) = Fake.sent - self.assertEqual('https://fa.brid.gy/ap/http://x.com/bob/followers#accept-http://x.com/follow', - bob_obj.key.id()) - self.assertEqual('http://x.com/alice:target', bob_target) - self.assertEqual('https://fa.brid.gy/ap/http://x.com/eve/followers#accept-http://x.com/follow', - eve_obj.key.id()) - self.assertEqual('http://x.com/alice:target', eve_target) + self.assertEqual([ + ('https://fa.brid.gy/ap/http://x.com/bob/followers#accept-http://x.com/follow', + 'http://x.com/alice:target'), + ('https://fa.brid.gy/ap/http://x.com/eve/followers#accept-http://x.com/follow', + 'http://x.com/alice:target'), + ], Fake.sent) self.assert_object('http://x.com/follow', our_as1=follow_as1, @@ -1601,3 +1598,60 @@ class ProtocolReceiveTest(TestCase): self.assertIn( "WARNING:protocol:actor fake:user isn't authed user fake:other", logs.output) + + @patch('oauth_dropins.webutil.appengine_config.tasks_client.create_task') + def test_post_create_send_tasks(self, mock_create_task): + common.RUN_TASKS_INLINE = False + + self.make_followers() + eve = self.make_user('other:eve', cls=OtherFake, obj_id='other:eve') + Follower.get_or_create(to=self.user, from_=eve) + + note_as1 = { + 'id': 'fake:post', + 'objectType': 'note', + 'author': 'fake:user', + } + self.assertEqual(('OK', 202), Fake.receive_as1(note_as1)) + + create_key = Object.get_by_id('fake:post#bridgy-fed-create').key.urlsafe() + self.assert_task(mock_create_task, 'send', '/queue/send', protocol='other', + obj=create_key, orig_obj='', url='other:eve:target', + user=self.user.key.urlsafe()) + self.assert_task(mock_create_task, 'send', '/queue/send', protocol='fake', + obj=create_key, orig_obj='', url='shared:target', + user=self.user.key.urlsafe()) + + self.assertEqual([], OtherFake.sent) + self.assertEqual([], Fake.sent) + + @patch('oauth_dropins.webutil.appengine_config.tasks_client.create_task') + def test_reply_send_tasks_orig_obj(self, mock_create_task): + common.RUN_TASKS_INLINE = False + + reply_as1 = { + 'id': 'fake:reply', + 'objectType': 'note', + 'inReplyTo': 'fake:post', + 'author': 'fake:user', + } + Fake.fetchable['fake:post'] = { + 'objectType': 'note', + 'id': 'fake:post', + 'author': 'fake:bob', + } + self.assertEqual(('OK', 202), Fake.receive_as1(reply_as1)) + + self.assert_object('fake:reply', + our_as1=reply_as1, + type='note', + ) + + create_key = Object(id='fake:reply#bridgy-fed-create').key.urlsafe() + orig_obj_key = Object(id='fake:post').key.urlsafe() + self.assert_task(mock_create_task, 'send', '/queue/send', protocol='fake', + obj=create_key, orig_obj=orig_obj_key, url='fake:post:target', + user=self.user.key.urlsafe()) + + self.assertEqual([], OtherFake.sent) + self.assertEqual([], Fake.sent) diff --git a/tests/test_web.py b/tests/test_web.py index b5e5e60..bf9e603 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -16,6 +16,7 @@ from werkzeug.exceptions import BadGateway, BadRequest from . import testutil from activitypub import ActivityPub, postprocess_as2 +import common from common import CONTENT_TYPE_HTML from models import Follower, Object from web import TASKS_LOCATION, Web @@ -520,6 +521,7 @@ class WebTest(TestCase): @patch('oauth_dropins.webutil.appengine_config.tasks_client.create_task') def test_make_task(self, mock_create_task, mock_get, mock_post): + common.RUN_TASKS_INLINE = False mock_get.side_effect = [NOTE, ACTOR] params = { @@ -700,7 +702,7 @@ class WebTest(TestCase): 'source': 'https://user.com/reply', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) mock_get.assert_has_calls(( self.req('https://user.com/reply'), @@ -745,7 +747,7 @@ class WebTest(TestCase): 'source': 'https://user.com/reply', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) self.assertEqual(1, mock_post.call_count) args, kwargs = mock_post.call_args @@ -763,7 +765,7 @@ class WebTest(TestCase): 'source': 'https://user.com/repost', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) self.assert_deliveries(mock_post, ['https://mas.to/inbox'], REPOST_AS2, ignore=['cc']) @@ -793,7 +795,7 @@ class WebTest(TestCase): 'target': 'https://fed.brid.gy/', 'force': '', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) args, kwargs = mock_post.call_args self.assertEqual(('https://mas.to/inbox',), args) @@ -824,7 +826,7 @@ class WebTest(TestCase): 'source': 'https://user.com/reply', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) mock_get.assert_has_calls(( self.req('https://user.com/reply'), @@ -858,7 +860,7 @@ class WebTest(TestCase): 'source': 'https://user.com/repost', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) mock_get.assert_has_calls(( self.req('https://user.com/repost'), @@ -905,7 +907,7 @@ class WebTest(TestCase): 'source': 'https://user.com/reply', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) mock_get.assert_has_calls(( self.req('https://user.com/reply'), @@ -961,7 +963,7 @@ class WebTest(TestCase): 'source': 'https://user.com/multiple', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) inboxes = ['https://inbox/', 'https://public/inbox', 'https://shared/inbox'] expected = { @@ -997,7 +999,7 @@ class WebTest(TestCase): 'source': 'https://user.com/repost', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) args, kwargs = mock_post.call_args self.assertEqual(('https://mas.to/inbox',), args) @@ -1024,7 +1026,7 @@ class WebTest(TestCase): 'source': 'https://user.com/repost', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) args, kwargs = mock_post.call_args self.assertEqual(('https://mas.to/inbox',), args) @@ -1051,7 +1053,7 @@ class WebTest(TestCase): 'source': 'https://user.com/repost', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) repost_mf2 = copy.deepcopy(REPOST_MF2) repost_mf2['properties']['author'] = ['https://user.com/'] @@ -1084,7 +1086,7 @@ class WebTest(TestCase): 'source': 'https://user.com/post', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) inboxes = ['https://inbox/', 'https://public/inbox', 'https://shared/inbox'] self.assert_object('https://user.com/post#bridgy-fed-create', @@ -1121,7 +1123,7 @@ class WebTest(TestCase): 'source': 'https://www.user.com/post', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) mock_get.assert_has_calls(( self.req('https://www.user.com/post'), @@ -1145,7 +1147,7 @@ class WebTest(TestCase): 'source': 'https://user.com/post', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) mock_get.assert_has_calls(( self.req('https://user.com/post'), @@ -1182,7 +1184,7 @@ class WebTest(TestCase): 'source': 'https://user.com/post', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) mock_get.assert_has_calls(( self.req('https://user.com/post'), @@ -1229,7 +1231,7 @@ class WebTest(TestCase): 'source': 'https://user.com/post', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) self.assertEqual(('https://inbox/',), mock_post.call_args[0]) create = copy.deepcopy(CREATE_AS2) @@ -1247,7 +1249,7 @@ class WebTest(TestCase): 'source': 'https://user.com/follow', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) mock_get.assert_has_calls(( self.req('https://user.com/follow'), @@ -1301,7 +1303,7 @@ class WebTest(TestCase): 'source': 'https://user.com/follow', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) args, kwargs = mock_post.call_args self.assertEqual(('https://mas.to/inbox',), args) @@ -1333,7 +1335,7 @@ class WebTest(TestCase): 'source': 'https://user.com/follow#2', 'target': 'https://fed.brid.gy/', }) - self.assert_equals(200, got.status_code) + self.assert_equals(202, got.status_code) mock_get.assert_has_calls(( self.req('https://user.com/follow'), @@ -1385,7 +1387,7 @@ class WebTest(TestCase): 'source': 'https://user.com/follow', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) mock_get.assert_has_calls(( self.req('https://user.com/follow'), @@ -1458,7 +1460,7 @@ class WebTest(TestCase): 'source': 'https://user.com/post', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code, got.text) + self.assertEqual(202, got.status_code, got.text) inboxes = ('https://inbox/', 'https://public/inbox', 'https://shared/inbox') self.assert_deliveries(mock_post, inboxes, DELETE_AS2) @@ -1514,11 +1516,7 @@ class WebTest(TestCase): 'source': 'https://user.com/follow', 'target': 'https://fed.brid.gy/', }) - body = got.get_data(as_text=True) - self.assertEqual(502, got.status_code, body) - self.assertIn( - '405 Client Error: None for url: https://mas.to/inbox ; abc xyz', - body) + self.assertEqual(202, got.status_code) mock_get.assert_has_calls(( self.req('https://user.com/follow'), @@ -1574,7 +1572,7 @@ class WebTest(TestCase): 'source': 'https://user.com/', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) mock_get.assert_has_calls(( self.req('https://user.com/'), )) @@ -1661,7 +1659,7 @@ class WebTest(TestCase): 'source': 'https://user.com/like', 'target': 'https://fed.brid.gy/', }) - self.assertEqual(200, got.status_code) + self.assertEqual(202, got.status_code) self.assertIn( "WARNING:models:actor https://user.com/ isn't https://user.com/like's author or actor ['https://eve.com/']", diff --git a/tests/testutil.py b/tests/testutil.py index 679b360..6638f54 100644 --- a/tests/testutil.py +++ b/tests/testutil.py @@ -106,9 +106,9 @@ class Fake(User, protocol.Protocol): return url.startswith(f'{cls.LABEL}:blocklisted') @classmethod - def send(cls, obj, url, orig_obj=None, log_data=True): + def send(cls, obj, url, orig_obj=None): logger.info(f'{cls.__name__}.send {url}') - cls.sent.append((obj, url)) + cls.sent.append((obj.key.id(), url)) return True @classmethod @@ -185,6 +185,7 @@ class TestCase(unittest.TestCase, testutil.Asserts): appengine_info.APP_ID = 'my-app' appengine_info.LOCAL_SERVER = False + common.RUN_TASKS_INLINE = True app.testing = True cache.clear() protocol.seen_ids.clear() @@ -477,7 +478,7 @@ class TestCase(unittest.TestCase, testutil.Asserts): 'app_engine_http_request': { 'http_method': 'POST', 'relative_uri': path, - 'body': urlencode(params).encode(), + 'body': urlencode(sorted(params.items())).encode(), 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, }, },