kopia lustrzana https://github.com/snarfed/bridgy-fed
rodzic
8bbcf2d6bd
commit
db646909e2
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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'},
|
||||
},
|
||||
})
|
||||
|
|
158
protocol.py
158
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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/']",
|
||||
|
|
|
@ -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'},
|
||||
},
|
||||
},
|
||||
|
|
Ładowanie…
Reference in New Issue