kopia lustrzana https://github.com/snarfed/bridgy-fed
				
				
				
			
		
			
				
	
	
		
			3559 wiersze
		
	
	
		
			126 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			3559 wiersze
		
	
	
		
			126 KiB
		
	
	
	
		
			Python
		
	
	
| """Unit tests for protocol.py."""
 | |
| import copy
 | |
| from datetime import timedelta
 | |
| import logging
 | |
| from threading import Condition, Thread
 | |
| from unittest import skip
 | |
| from unittest.mock import ANY, patch
 | |
| 
 | |
| from arroba.tests.testutil import dns_answer
 | |
| from google.cloud import ndb
 | |
| from google.cloud.ndb.global_cache import _InProcessGlobalCache
 | |
| from granary import as2
 | |
| from granary.tests.test_bluesky import ACTOR_PROFILE_BSKY
 | |
| from oauth_dropins.webutil import appengine_info, util
 | |
| from oauth_dropins.webutil.appengine_config import ndb_client
 | |
| from oauth_dropins.webutil.flask_util import NoContent
 | |
| from oauth_dropins.webutil.testutil import NOW, requests_response
 | |
| from oauth_dropins.webutil.util import json_dumps
 | |
| import requests
 | |
| from werkzeug.exceptions import BadRequest
 | |
| 
 | |
| # import first so that Fake is defined before URL routes are registered
 | |
| from .testutil import ExplicitFake, Fake, OtherFake, TestCase
 | |
| 
 | |
| from activitypub import ActivityPub
 | |
| from app import app
 | |
| from atproto import ATProto
 | |
| import common
 | |
| import memcache
 | |
| import models
 | |
| from models import DM, Follower, Object, PROTOCOLS, Target, User
 | |
| import protocol
 | |
| from protocol import ErrorButDoNotRetryTask, Protocol
 | |
| from ui import UIProtocol
 | |
| from web import Web
 | |
| 
 | |
| from .test_activitypub import ACTOR, NOTE
 | |
| from .test_atproto import DID_DOC
 | |
| from .test_dms import DmsTest
 | |
| from .test_web import (
 | |
|     ACTOR_HTML_RESP,
 | |
|     ACTOR_AS1_UNWRAPPED_URLS,
 | |
|     ACTOR_MF2_REL_URLS,
 | |
|     NOTE as NOTE_HTML_RESP,
 | |
|     web_user_gets,
 | |
| )
 | |
| 
 | |
| 
 | |
| class ProtocolTest(TestCase):
 | |
| 
 | |
|     def setUp(self):
 | |
|         super().setUp()
 | |
|         self.user = self.make_user('foo.com', cls=Web, has_hcard=True)
 | |
| 
 | |
|     def tearDown(self):
 | |
|         PROTOCOLS.pop('greedy', None)
 | |
|         super().tearDown()
 | |
| 
 | |
|     def test_protocols_global(self):
 | |
|         self.assertEqual(Fake, PROTOCOLS['fake'])
 | |
|         self.assertEqual(Web, PROTOCOLS['web'])
 | |
|         self.assertEqual(Web, PROTOCOLS['webmention'])
 | |
| 
 | |
|     def test_for_bridgy_subdomain_for_request(self):
 | |
|         for domain, expected in [
 | |
|                 ('fake.brid.gy', Fake),
 | |
|                 ('ap.brid.gy', ActivityPub),
 | |
|                 ('activitypub.brid.gy', ActivityPub),
 | |
|                 ('web.brid.gy', Web),
 | |
|                 (None, None),
 | |
|                 ('', None),
 | |
|                 ('brid.gy', None),
 | |
|                 ('www.brid.gy', None),
 | |
|                 ('fed.brid.gy', None),
 | |
|                 ('fake.fed.brid.gy', None),
 | |
|                 ('fake', None),
 | |
|                 ('fake.com', None),
 | |
|         ]:
 | |
|             with self.subTest(domain=domain, expected=expected):
 | |
|                 self.assertEqual(expected, Protocol.for_bridgy_subdomain(domain))
 | |
|                 with app.test_request_context('/foo', base_url=f'https://{domain}/'):
 | |
|                     self.assertEqual(expected, Protocol.for_request())
 | |
| 
 | |
|     def test_for_bridgy_subdomain_for_request_fed(self):
 | |
|         for url, expected in [
 | |
|             ('https://fed.brid.gy/', Fake),
 | |
|             ('http://localhost/foo', Fake),
 | |
|             ('https://ap.brid.gy/bar', ActivityPub),
 | |
|             ('https://baz/biff', None),
 | |
|         ]:
 | |
|             with self.subTest(url=url, expected=expected):
 | |
|                 self.assertEqual(expected,
 | |
|                                  Protocol.for_bridgy_subdomain(url, fed=Fake))
 | |
|                 self.assertEqual(expected,
 | |
|                                  Protocol.for_bridgy_subdomain(url, fed='fake'))
 | |
|                 with app.test_request_context('/foo', base_url=url):
 | |
|                     self.assertEqual(expected, Protocol.for_request(fed=Fake))
 | |
| 
 | |
|     def test_for_id(self):
 | |
|         for id, expected in [
 | |
|                 (None, None),
 | |
|                 ('', None),
 | |
|                 ('foo://bar', None),
 | |
|                 ('https:/abc/', None),
 | |
|                 ('https:/[DOMAIN]/', None),
 | |
|                 ('https://[DOMAIN]/', None),
 | |
|                 ('fake:foo', Fake),
 | |
|                 ('at://foo', ATProto),
 | |
|                 # TODO: remove? should we require normalized ids?
 | |
|                 ('https://ap.brid.gy/foo/bar', ActivityPub),
 | |
|                 ('https://web.brid.gy/foo/bar', Web),
 | |
|                 ('https://fed.brid.gy/', Web),
 | |
|                 ('https://web.brid.gy/', Web),
 | |
|                 ('https://bsky.brid.gy/', Web),
 | |
|                 ('bsky.brid.gy', Web),
 | |
|         ]:
 | |
|             self.assertEqual(expected, Protocol.for_id(id, remote=False))
 | |
|             self.assertEqual(expected, Protocol.for_id(id, remote=True))
 | |
| 
 | |
|     def test_for_id_true_overrides_none(self):
 | |
|         class Greedy(Protocol, User):
 | |
|             @classmethod
 | |
|             def owns_id(cls, id):
 | |
|                 return True
 | |
| 
 | |
|         self.assertEqual(Greedy, Protocol.for_id('http://foo'))
 | |
|         self.assertEqual(Greedy, Protocol.for_id('https://bar/baz'))
 | |
| 
 | |
|     def test_for_id_object(self):
 | |
|         self.store_object(id='http://ui.org/obj', source_protocol='ui')
 | |
|         self.assertEqual(UIProtocol, Protocol.for_id('http://ui.org/obj'))
 | |
| 
 | |
|     @patch('requests.get', return_value=requests_response())
 | |
|     def test_for_id_object_missing_source_protocol(self, _):
 | |
|         self.store_object(id='http://b.ad/obj')
 | |
|         self.assertIsNone(Protocol.for_id('http://b.ad/obj'))
 | |
| 
 | |
|     @patch('requests.get')
 | |
|     def test_for_id_activitypub_fetch(self, mock_get):
 | |
|         mock_get.return_value = self.as2_resp(ACTOR)
 | |
|         self.assertEqual(ActivityPub, Protocol.for_id('http://ap.org/actor'))
 | |
|         self.assertIn(self.as2_req('http://ap.org/actor'), mock_get.mock_calls)
 | |
| 
 | |
|     @patch('requests.get')
 | |
|     def test_for_id_activitypub_fetch_fails(self, mock_get):
 | |
|         mock_get.return_value = requests_response('', status=403)
 | |
|         self.assertIsNone(Protocol.for_id('http://ap.org/actor'))
 | |
|         self.assertIn(self.as2_req('http://ap.org/actor'), mock_get.mock_calls)
 | |
|         mock_get.assert_called_once()
 | |
| 
 | |
|     @patch('requests.get')
 | |
|     def test_for_id_web_fetch(self, mock_get):
 | |
|         mock_get.return_value = ACTOR_HTML_RESP
 | |
|         self.assertEqual(Web, Protocol.for_id('http://web.site/'))
 | |
|         self.assertIn(self.req('http://web.site/'), mock_get.mock_calls)
 | |
| 
 | |
|     @patch('requests.get')
 | |
|     def test_for_id_web_fetch_not_html(self, mock_get):
 | |
|         mock_get.return_value = requests_response('not html', content_type='text/abc')
 | |
|         self.assertIsNone(Protocol.for_id('http://web.site/xyz'))
 | |
|         self.assertIsNone(Object.get_by_id('http://web.site/xyz'))
 | |
|         self.assertIn(self.req('http://web.site/xyz'), mock_get.mock_calls)
 | |
| 
 | |
|     @patch('requests.get')
 | |
|     def test_for_id_web_fetch_no_mf2(self, mock_get):
 | |
|         mock_get.return_value = requests_response('<html></html>')
 | |
|         self.assertEqual(Web, Protocol.for_id('http://web.site/xyz'))
 | |
| 
 | |
|         obj = self.assert_object('http://web.site/xyz', source_protocol='web')
 | |
|         self.assertIsNone(obj.mf2)
 | |
|         self.assertIsNone(obj.as1)
 | |
| 
 | |
|         self.assertIn(self.req('http://web.site/xyz'), mock_get.mock_calls)
 | |
| 
 | |
|     @patch('requests.get')
 | |
|     def test_for_id_web_remote_false(self, mock_get):
 | |
|         self.assertIsNone(Protocol.for_id('http://web.site/', remote=False))
 | |
|         mock_get.assert_not_called()
 | |
| 
 | |
|     @patch('requests.get')
 | |
|     def test_for_id_synthetic(self, mock_get):
 | |
|         self.assertEqual(ATProto, Protocol.for_id('at://did/coll/rkey#bridgy-fed-xyz'))
 | |
|         self.assertEqual(Fake, Protocol.for_id('fake:post#bridgy-fed-delete-abc'))
 | |
| 
 | |
|         Object(id='http://in.st/post', source_protocol='activitypub').put()
 | |
|         self.assertEqual(ActivityPub,
 | |
|                          Protocol.for_id('http://in.st/post#bridgy-fed-a'))
 | |
| 
 | |
|         mock_get.assert_not_called()
 | |
| 
 | |
|     def test_for_handle_deterministic(self):
 | |
|         for handle, expected in [
 | |
|             (None, (None, None)),
 | |
|             ('', (None, None)),
 | |
|             ('foo://bar', (None, None)),
 | |
|             ('fake:foo', (None, None)),
 | |
|             ('fake:handle:foo', (Fake, None)),
 | |
|             ('@me@foo', (ActivityPub, None)),
 | |
|         ]:
 | |
|             self.assertEqual(expected, Protocol.for_handle(handle))
 | |
| 
 | |
|     def test_for_handle_stored_user(self):
 | |
|         user = self.make_user(id='user.com', cls=Web)
 | |
|         self.assertEqual('user.com', user.handle)
 | |
|         self.assertEqual((Web, 'user.com'), Protocol.for_handle('user.com'))
 | |
| 
 | |
|     def test_for_handle_opted_out_user(self):
 | |
|         user = self.make_user(id='user.com', cls=Web, obj_as1={'summary': '#nobot'})
 | |
|         self.assertEqual('user.com', user.handle)
 | |
|         self.assertEqual((None, None), Protocol.for_handle('user.com'))
 | |
| 
 | |
|     @patch('dns.resolver.resolve', return_value = dns_answer(
 | |
|             '_atproto.ha.nl.', '"did=did:plc:123abc"'))
 | |
|     def test_for_handle_atproto_resolve(self, _):
 | |
|         self.assertEqual((ATProto, 'did:plc:123abc'), Protocol.for_handle('ha.nl'))
 | |
| 
 | |
|     def test_load(self):
 | |
|         Fake.fetchable['foo'] = {'x': 'y'}
 | |
| 
 | |
|         loaded = Fake.load('foo')
 | |
|         self.assertEqual({
 | |
|             'id': 'foo',
 | |
|             'x': 'y',
 | |
|         }, loaded.our_as1)
 | |
|         self.assertFalse(loaded.changed)
 | |
|         self.assertTrue(loaded.new)
 | |
| 
 | |
|         self.assertIsNotNone(Object.get_by_id('foo'))
 | |
|         self.assertEqual(['foo'], Fake.fetched)
 | |
| 
 | |
|     def test_load_existing(self):
 | |
|         self.store_object(id='foo', our_as1={'x': 'y'})
 | |
| 
 | |
|         loaded = Fake.load('foo')
 | |
|         self.assertEqual({
 | |
|             'id': 'foo',
 | |
|             'x': 'y',
 | |
|         }, loaded.our_as1)
 | |
|         self.assertFalse(loaded.changed)
 | |
|         self.assertFalse(loaded.new)
 | |
| 
 | |
|         self.assertEqual([], Fake.fetched)
 | |
| 
 | |
|     def test_load_existing_empty_deleted(self):
 | |
|         stored = self.store_object(id='foo', deleted=True)
 | |
| 
 | |
|         loaded = Fake.load('foo')
 | |
|         self.assert_entities_equal(stored, loaded)
 | |
|         self.assertFalse(loaded.changed)
 | |
|         self.assertFalse(loaded.new)
 | |
| 
 | |
|         self.assertEqual([], Fake.fetched)
 | |
| 
 | |
|     def test_load_remote_true_existing_empty(self):
 | |
|         Fake.fetchable['foo'] = {'x': 'y'}
 | |
|         Object(id='foo', our_as1={}).put()
 | |
| 
 | |
|         loaded = Fake.load('foo', remote=True)
 | |
|         self.assertEqual({'id': 'foo', 'x': 'y'}, loaded.as1)
 | |
|         self.assertTrue(loaded.changed)
 | |
|         self.assertFalse(loaded.new)
 | |
|         self.assertEqual(['foo'], Fake.fetched)
 | |
| 
 | |
|     def test_load_remote_true_new_empty(self):
 | |
|         Fake.fetchable['foo'] = None
 | |
|         self.store_object(id='foo', our_as1={'x': 'y'})
 | |
| 
 | |
|         loaded = Fake.load('foo', remote=True)
 | |
|         self.assertIsNone(loaded.as1)
 | |
|         self.assertTrue(loaded.changed)
 | |
|         self.assertFalse(loaded.new)
 | |
|         self.assertEqual(['foo'], Fake.fetched)
 | |
| 
 | |
|     def test_load_remote_true_unchanged(self):
 | |
|         obj = self.store_object(id='fake:foo', our_as1={'x': 'stored'},
 | |
|                                 source_protocol='fake')
 | |
|         Fake.fetchable['fake:foo'] = {'x': 'stored'}
 | |
| 
 | |
|         loaded = Fake.load('fake:foo', remote=True)
 | |
|         self.assert_entities_equal(obj, loaded,
 | |
|                                    ignore=['expire', 'created', 'updated'])
 | |
|         self.assertFalse(loaded.changed)
 | |
|         self.assertFalse(loaded.new)
 | |
|         self.assertEqual(['fake:foo'], Fake.fetched)
 | |
| 
 | |
|     def test_load_remote_true_local_false(self):
 | |
|         Fake.fetchable['foo'] = our_as1={'x': 'y'}
 | |
| 
 | |
|         loaded = Fake.load('foo', local=False, remote=True)
 | |
|         self.assertEqual({'id': 'foo', 'x': 'y'}, loaded.as1)
 | |
|         self.assertIsNone(loaded.changed)
 | |
|         self.assertIsNone(loaded.new)
 | |
|         self.assertEqual(['foo'], Fake.fetched)
 | |
| 
 | |
|     def test_load_remote_true_changed(self):
 | |
|         self.store_object(id='foo', our_as1={'content': 'stored'})
 | |
|         Fake.fetchable['foo'] = {'content': 'new'}
 | |
| 
 | |
|         loaded = Fake.load('foo', remote=True)
 | |
|         self.assertEqual({
 | |
|             'id': 'foo',
 | |
|             'content': 'new',
 | |
|         }, loaded.our_as1)
 | |
|         self.assertTrue(loaded.changed)
 | |
|         self.assertFalse(loaded.new)
 | |
|         self.assertEqual(['foo'], Fake.fetched)
 | |
| 
 | |
|     @patch('requests.get', return_value=ACTOR_HTML_RESP)
 | |
|     def test_load_remote_true_clear_our_as1(self, _):
 | |
|         self.store_object(id='https://f.ooo', our_as1={'should': 'disappear'},
 | |
|                           source_protocol='web')
 | |
| 
 | |
|         expected_mf2 = {
 | |
|             'type': ['h-card'],
 | |
|             'properties': {
 | |
|                 'url': ['https://f.ooo'],
 | |
|                 'name': ['Ms. ☕ Baz'],
 | |
|             },
 | |
|             'rel-urls': {
 | |
|                 'https://f.ooo': {'rels': ['me'], 'text': 'Ms. ☕ Baz'},
 | |
|                 'https://user.com/webmention': {'rels': ['webmention'], 'text': ''},
 | |
|             },
 | |
|             'url': 'https://f.ooo',
 | |
|         }
 | |
| 
 | |
|         loaded = Web.load('https://f.ooo', remote=True)
 | |
|         self.assertEqual(expected_mf2, loaded.mf2)
 | |
|         self.assertIsNone(loaded.our_as1)
 | |
|         self.assertEqual({
 | |
|             'objectType': 'person',
 | |
|             'id': 'https://f.ooo',
 | |
|             'url': 'https://f.ooo',
 | |
|             'displayName': 'Ms. ☕ Baz',
 | |
|             'urls': [{'value': 'https://f.ooo', 'displayName': 'Ms. ☕ Baz'}],
 | |
|         }
 | |
| , loaded.as1)
 | |
|         self.assertTrue(loaded.changed)
 | |
|         self.assertFalse(loaded.new)
 | |
| 
 | |
|     def test_load_remote_false(self):
 | |
|         self.assertIsNone(Fake.load('nope', remote=False))
 | |
|         self.assertEqual([], Fake.fetched)
 | |
| 
 | |
|         obj = self.store_object(id='foo', our_as1={'content': 'stored'})
 | |
|         self.assert_entities_equal(obj, Fake.load('foo', remote=False))
 | |
|         self.assertEqual([], Fake.fetched)
 | |
| 
 | |
|     def test_load_remote_false_existing_object_empty(self):
 | |
|         obj = self.store_object(id='foo')
 | |
|         self.assert_entities_equal(obj, Protocol.load('foo', remote=False))
 | |
| 
 | |
|     def test_load_local_false_missing(self):
 | |
|         self.assertIsNone(Fake.load('foo', local=False))
 | |
|         self.assertEqual(['foo'], Fake.fetched)
 | |
| 
 | |
|     def test_load_local_false_existing(self):
 | |
|         self.store_object(id='foo', our_as1={'content': 'stored'}, source_protocol='ui')
 | |
| 
 | |
|         Fake.fetchable['foo'] = {'foo': 'bar'}
 | |
|         Fake.load('foo', local=False)
 | |
|         self.assert_object('foo', source_protocol='fake', our_as1={'foo': 'bar'})
 | |
|         self.assertEqual(['foo'], Fake.fetched)
 | |
| 
 | |
|     def test_load_remote_false_local_false_assert(self):
 | |
|         with self.assertRaises(AssertionError):
 | |
|             Fake.load('nope', local=False, remote=False)
 | |
| 
 | |
|     def test_load_resolve_ids(self):
 | |
|         follow = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'id': 'fake:follow',
 | |
|             'actor': 'fake:alice',
 | |
|             'object': 'fake:bob',
 | |
|         }
 | |
|         Fake.fetchable = {
 | |
|             'fake:follow': follow,
 | |
|         }
 | |
| 
 | |
|         # no matching copy users
 | |
|         obj = Fake.load('fake:follow', remote=True)
 | |
|         self.assert_equals(follow, obj.our_as1)
 | |
| 
 | |
|         # matching copy user
 | |
|         self.make_user('other:bob', cls=OtherFake,
 | |
|                        copies=[Target(uri='fake:bob', protocol='fake')])
 | |
| 
 | |
|         obj = Fake.load('fake:follow', remote=True)
 | |
|         self.assert_equals({
 | |
|             **follow,
 | |
|             'id': 'fake:follow',
 | |
|             'object': 'other:bob',
 | |
|         }, obj.our_as1)
 | |
| 
 | |
|     def test_load_preserves_fragment(self):
 | |
|         stored = self.store_object(id='http://the/id#frag', our_as1={'foo': 'bar'})
 | |
|         got = ActivityPub.load('http://the/id#frag')
 | |
|         self.assert_entities_equal(stored, got)
 | |
|         self.assertEqual([], Fake.fetched)
 | |
| 
 | |
|     def test_load_refresh(self):
 | |
|         Fake.fetchable['foo'] = {'fetched': 'x'}
 | |
| 
 | |
|         too_old = (NOW.replace(tzinfo=None)
 | |
|                    - protocol.OBJECT_REFRESH_AGE
 | |
|                    - timedelta(days=1))
 | |
|         with patch('models.Object.updated._now', return_value=too_old):
 | |
|             obj = Object(id='foo', our_as1={'orig': 'y'})
 | |
|             obj.put()
 | |
| 
 | |
|         loaded = Fake.load('foo')
 | |
|         self.assertEqual({'fetched': 'x', 'id': 'foo'}, loaded.our_as1)
 | |
| 
 | |
|     @patch('oauth_dropins.webutil.models.MAX_ENTITY_SIZE', new=50)
 | |
|     def test_load_too_big(self):
 | |
|         Fake.fetchable['fake:foo'] = {
 | |
|             'objectType': 'note',
 | |
|             'content': 'a bit of text that makes sure we end up over the limit ',
 | |
|         }
 | |
|         self.assertIsNone(Fake.load('fake:foo'))
 | |
| 
 | |
|     def test_actor_key(self):
 | |
|         user = self.make_user(id='fake:a', cls=Fake)
 | |
|         a_key = user.key
 | |
| 
 | |
|         for expected, obj in [
 | |
|                 (None, Object()),
 | |
|                 (None, Object(our_as1={})),
 | |
|                 (None, Object(our_as1={'foo': 'bar'})),
 | |
|                 (None, Object(our_as1={'foo': 'bar'})),
 | |
|                 (None, Object(our_as1={'actor': ''})),
 | |
|                 (a_key, Object(our_as1={'actor': 'fake:a'})),
 | |
|                 (a_key, Object(our_as1={'author': 'fake:a'})),
 | |
|         ]:
 | |
|             self.assertEqual(expected, Fake.actor_key(obj))
 | |
| 
 | |
|         self.assertIsNone(Fake.actor_key(Object()))
 | |
| 
 | |
|     def test_key_for(self):
 | |
|         self.assertEqual(self.user.key, Protocol.key_for(self.user.key.id()))
 | |
| 
 | |
|         user = Fake(id='fake:other', use_instead=self.user.key).put()
 | |
|         self.assertEqual(self.user.key, Protocol.key_for('fake:other'))
 | |
| 
 | |
|         # no stored user
 | |
|         self.assertEqual(ndb.Key('Fake', 'fake:foo'), Protocol.key_for('fake:foo'))
 | |
| 
 | |
|         self.user.obj.our_as1 = {'summary': '#nobridge'}
 | |
|         self.user.obj.put()
 | |
|         self.assertIsNone(Protocol.key_for(self.user.key.id()))
 | |
| 
 | |
|     def test_bridged_web_url_for(self):
 | |
|         self.assertIsNone(Protocol.bridged_web_url_for(self.user))
 | |
|         self.assertEqual('https://foo.com/',
 | |
|                          Protocol.bridged_web_url_for(self.user, fallback=True))
 | |
| 
 | |
|     def test_targets_checks_blocklisted_per_protocol(self):
 | |
|         """_targets should call the target protocol's is_blocklisted()."""
 | |
|         # non-ATProto account, ATProto target (PDS) is bsky.brid.gy
 | |
|         # shouldn't be blocklisted
 | |
|         user = self.make_user(id='fake:user', cls=Fake, enabled_protocols=['atproto'])
 | |
| 
 | |
|         did_doc = copy.deepcopy(DID_DOC)
 | |
|         did_doc['service'][0]['serviceEndpoint'] = 'http://localhost/'
 | |
|         self.store_object(id='did:plc:foo', raw=did_doc)
 | |
| 
 | |
|         # store Objects so we don't try to fetch them remotely
 | |
|         self.store_object(id='at://did:plc:foo/co.ll/post', our_as1={'foo': 'bar'})
 | |
|         self.store_object(id='fake:post', our_as1={'foo': 'baz'})
 | |
| 
 | |
|         obj = Object(source_protocol='other', our_as1={
 | |
|             'id': 'other:reply',
 | |
|             'objectType': 'note',
 | |
|             'inReplyTo': [
 | |
|                 'fake:post',
 | |
|                 'fake:blocklisted-post',
 | |
|                 'https://t.co/foo',
 | |
|                 'http://localhost/post',
 | |
|                 'at://did:plc:foo/co.ll/post',
 | |
|             ],
 | |
|         })
 | |
|         self.assertCountEqual([
 | |
|             Target(protocol='fake', uri='fake:post:target'),
 | |
|             Target(protocol='atproto', uri='https://atproto.brid.gy'),
 | |
|         ], Protocol.targets(obj, from_user=user).keys())
 | |
| 
 | |
|     def test_targets_undo_share_enabled_protocols(self):
 | |
|         # https://console.cloud.google.com/errors/detail/CJK54eaoneesMg;time=P30D?project=bridgy-federated
 | |
|         self.user = self.make_user('fake:user', cls=Fake)
 | |
| 
 | |
|         self.store_object(id='fake:orig', our_as1={'id': 'fake:orig'},
 | |
|                           copies=[Target(protocol='other', uri='other:orig')])
 | |
| 
 | |
|         share = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'share',
 | |
|             'id': 'fake:share',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'fake:orig',
 | |
|         }
 | |
|         Fake.fetchable['fake:share'] = share
 | |
| 
 | |
|         obj = Object(id='fake:undo', our_as1={
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'undo',
 | |
|             'actor': 'fake:user',
 | |
|             'object': share,
 | |
|         })
 | |
|         self.assertEqual({
 | |
|             Target(protocol='other', uri='other:orig:target'),
 | |
|         }, Fake.targets(obj, from_user=self.user).keys())
 | |
| 
 | |
|     def test_targets_composite_inreplyto(self):
 | |
|         Fake.fetchable['fake:post'] = {
 | |
|             'objectType': 'note',
 | |
|         }
 | |
| 
 | |
|         reply = Object(our_as1={
 | |
|             'id': 'other:reply',
 | |
|             'objectType': 'note',
 | |
|             'inReplyTo': {
 | |
|                 'id': 'fake:post',
 | |
|                 'url': 'http://foo',
 | |
|             },
 | |
|         })
 | |
|         create = Object(our_as1={
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'object': reply.as1,
 | |
|         })
 | |
| 
 | |
|         self.assertEqual(
 | |
|             {Target(protocol='fake', uri='fake:post:target')},
 | |
|             OtherFake.targets(create, crud_obj=reply, from_user=self.user).keys())
 | |
| 
 | |
|     def test_targets_link_tag_has_no_orig_obj(self):
 | |
|         # https://github.com/snarfed/bridgy-fed/issues/1237
 | |
|         Fake.fetchable['fake:linked-post'] = {
 | |
|             'objectType': 'note',
 | |
|         }
 | |
| 
 | |
|         note = Object(our_as1={
 | |
|             'objectType': 'note',
 | |
|             'id': 'fake:post',
 | |
|             'tags': [{'url': 'fake:linked-post'}],
 | |
|         })
 | |
|         create = Object(our_as1={
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'object': note.as1,
 | |
|         })
 | |
|         self.assertEqual(
 | |
|             {Target(protocol='fake', uri='fake:linked-post:target'): None},
 | |
|             OtherFake.targets(create, crud_obj=note, from_user=self.user))
 | |
| 
 | |
|     @patch.object(Fake, 'fetch')
 | |
|     def test_targets_continues_on_fetch_error(self, mock_fetch):
 | |
|         def fetch(obj, **_):
 | |
|             if obj.key.id() == 'fake:post-1':
 | |
|                 raise requests.ConnectionError('foo')
 | |
|             elif obj.key.id() == 'fake:post-2':
 | |
|                 obj.our_as1 = {
 | |
|                     'objectType': 'note',
 | |
|                     'id': 'fake:post-2',
 | |
|                     'author': 'fake:user',
 | |
|                 }
 | |
|                 return True
 | |
| 
 | |
|         mock_fetch.side_effect = fetch
 | |
| 
 | |
|         reply = Object(source_protocol='other', our_as1={
 | |
|             'id': 'other:reply',
 | |
|             'objectType': 'note',
 | |
|             'author': 'other:user',
 | |
|             'inReplyTo': [
 | |
|                 'fake:post-1',
 | |
|                 'fake:post-2',
 | |
|             ],
 | |
|         })
 | |
|         create = Object(source_protocol='other', our_as1={
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'object': reply.as1,
 | |
|         })
 | |
| 
 | |
|         self.assertEqual(
 | |
|             {Target(protocol='fake', uri='fake:post-2:target')},
 | |
|             OtherFake.targets(create, crud_obj=reply, from_user=self.user).keys())
 | |
| 
 | |
|     def test_translate_ids_follow(self):
 | |
|         self.assert_equals({
 | |
|             'id': 'other:o:fa:fake:follow',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'actor': 'other:u:fake:alice',
 | |
|             'object': 'other:u:fake:bob',
 | |
|         }, OtherFake.translate_ids({
 | |
|             'id': 'fake:follow',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'actor': 'fake:alice',
 | |
|             'object': 'fake:bob',
 | |
|         }))
 | |
| 
 | |
|     def test_translate_ids_reply(self):
 | |
|         self.assert_equals({
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'object': {
 | |
|                 'id': 'other:o:fa:fake:reply',
 | |
|                 'objectType': 'note',
 | |
|                 'inReplyTo': 'other:o:fa:fake:post',
 | |
|                 'author': 'other:u:fake:alice',
 | |
|                 'tags': [{
 | |
|                     'objectType': 'mention',
 | |
|                     'url': 'uri:other:u:fake:bob',
 | |
|                 }],
 | |
|             },
 | |
|         }, OtherFake.translate_ids({
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'object': {
 | |
|                 'id': 'fake:reply',
 | |
|                 'objectType': 'note',
 | |
|                 'inReplyTo': 'fake:post',
 | |
|                 'author': {'id': 'fake:alice'},
 | |
|                 'tags': [{
 | |
|                     'objectType': 'mention',
 | |
|                     'url': 'fake:bob',
 | |
|                 }],
 | |
|             },
 | |
|         }))
 | |
| 
 | |
|     def test_translate_ids_update_profile(self):
 | |
|         self.assert_equals({
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'update',
 | |
|             'actor': 'other:u:fake:alice',
 | |
|             'object': {
 | |
|                 'objectType': 'person',
 | |
|                 'id': 'other:u:fake:alice',
 | |
|             },
 | |
|         }, OtherFake.translate_ids({
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'update',
 | |
|             'actor': 'fake:alice',
 | |
|             'object': {
 | |
|                 'objectType': 'person',
 | |
|                 'id': 'fake:alice',
 | |
|             },
 | |
|         }))
 | |
| 
 | |
|     def test_translate_ids_delete_actor(self):
 | |
|         self.assert_equals({
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'delete',
 | |
|             'actor': 'other:u:fake:alice',
 | |
|             'object': 'other:u:fake:alice',
 | |
|         }, OtherFake.translate_ids({
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'delete',
 | |
|             'actor': 'fake:alice',
 | |
|             'object': 'fake:alice',
 | |
|         }))
 | |
| 
 | |
|     def test_translate_ids_attachments_mention_tags(self):
 | |
|         self.assert_equals({
 | |
|             'objectType': 'note',
 | |
|             'attachments': [
 | |
|                 {'id': 'other:o:fa:fake:123'},
 | |
|                 {'id': 'other:o:fa:fake:456',
 | |
|                  'url': 'fake:456'},
 | |
|             ],
 | |
|             'tags': [
 | |
|                 {'objectType': 'mention', 'url': 'uri:other:u:fake:alice'},
 | |
|                 {'url': 'fake:000'},
 | |
|             ],
 | |
|         }, OtherFake.translate_ids({
 | |
|             'objectType': 'note',
 | |
|             'attachments': [
 | |
|                 {'id': 'fake:123'},
 | |
|                 {'url': 'fake:456'},
 | |
|             ],
 | |
|             'tags': [
 | |
|                 {'objectType': 'mention', 'url': 'fake:alice'},
 | |
|                 {'url': 'fake:000'},
 | |
|             ],
 | |
|         }))
 | |
| 
 | |
|     def test_translate_ids_attachment_url_blocklisted(self):
 | |
|         self.assert_equals({
 | |
|             'objectType': 'note',
 | |
|             'attachments': [{'url': 'https://t.co/foo'}],
 | |
|         }, OtherFake.translate_ids({
 | |
|             'objectType': 'note',
 | |
|             'attachments': [{'url': 'https://t.co/foo'}],
 | |
|         }))
 | |
| 
 | |
|     def test_translate_ids_copies(self):
 | |
|         self.store_object(id='fake:post',
 | |
|                           copies=[Target(uri='other:post', protocol='other')])
 | |
|         self.make_user('other:user', cls=OtherFake,
 | |
|                        copies=[Target(uri='fake:user', protocol='fake')])
 | |
| 
 | |
|         self.assert_equals({
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'actor': 'other:user',
 | |
|             'object': {
 | |
|                 'id': 'other:o:fa:fake:reply',
 | |
|                 'inReplyTo': 'other:post',
 | |
|             },
 | |
|             'attachments': [{
 | |
|                 'objectType': 'note',
 | |
|                 'id': 'other:post',
 | |
|                 'url': 'fake:post',
 | |
|             }],
 | |
|         }, OtherFake.translate_ids({
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'actor': 'fake:user',
 | |
|             'object': {
 | |
|                 'id': 'fake:reply',
 | |
|                 'inReplyTo': 'fake:post',
 | |
|             },
 | |
|             'attachments': [{
 | |
|                 'objectType': 'note',
 | |
|                 'url': 'fake:post',
 | |
|             }],
 | |
|         }))
 | |
| 
 | |
|     def test_translate_ids_multiple_objects(self):
 | |
|         self.assert_equals({
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'flag',
 | |
|             'object': [
 | |
|                 'other:eve',
 | |
|                 'other:o:fa:fake:bob',
 | |
|             ],
 | |
|         }, OtherFake.translate_ids({
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'flag',
 | |
|             'object': [
 | |
|                 'other:eve',
 | |
|                 'fake:bob',
 | |
|             ]
 | |
|         }))
 | |
| 
 | |
|     def test_translate_ids_inner_object_object_id(self):
 | |
|         # https://github.com/snarfed/bridgy-fed/issues/1492
 | |
|         self.assert_equals({
 | |
|             'object': {
 | |
|                 'verb': 'flag',
 | |
|                 'object': 'other:o:fa:fake:post',
 | |
|             },
 | |
|         }, OtherFake.translate_ids({
 | |
|             'object': {
 | |
|                 'verb': 'flag',
 | |
|                 'object': 'fake:post',
 | |
|             },
 | |
|         }))
 | |
| 
 | |
|     def test_translate_ids_inner_object_actor_id(self):
 | |
|         # https://github.com/snarfed/bridgy-fed/issues/1492
 | |
|         self.assert_equals({
 | |
|             'object': {
 | |
|                 'verb': 'follow',
 | |
|                 'object': 'other:u:fake:bob',
 | |
|             },
 | |
|         }, OtherFake.translate_ids({
 | |
|             'object': {
 | |
|                 'verb': 'follow',
 | |
|                 'object': 'fake:bob',
 | |
|             },
 | |
|         }))
 | |
| 
 | |
|     def test_translate_ids_to_cc(self):
 | |
|         self.assert_equals({
 | |
|             'id': 'xyz',
 | |
|             'to': ['other:u:fake:alice', 'other:bob'],
 | |
|             'cc': ['other:u:efake:eve', as2.PUBLIC_AUDIENCE],
 | |
|         }, OtherFake.translate_ids({
 | |
|             'id': 'xyz',
 | |
|             'to': ['fake:alice', 'other:bob'],
 | |
|             'cc': ['efake:eve', as2.PUBLIC_AUDIENCE],
 | |
|         }))
 | |
| 
 | |
|     def test_translate_ids_empty(self):
 | |
|         self.assertEqual({}, Fake.translate_ids({}))
 | |
| 
 | |
|     def test_translate_ids_single_inReplyTo(self):
 | |
|         obj = {'inReplyTo': 'foo'}
 | |
|         self.assertEqual(obj, Fake.translate_ids(obj))
 | |
| 
 | |
|     def test_translate_ids_multiple_inReplyTo(self):
 | |
|         obj = {'inReplyTo': ['foo', 'bar']}
 | |
|         self.assertEqual(obj, Fake.translate_ids(obj))
 | |
| 
 | |
|     def test_convert_object_is_from_user_adds_source_links(self):
 | |
|         alice = Fake(id='fake:alice')
 | |
|         self.assertEqual({
 | |
|             'objectType': 'person',
 | |
|             'id': 'other:u:fake:alice',
 | |
|             'url': 'http://unused',
 | |
|             'summary': 'something about me<br><br>[<a href="https://fed.brid.gy/fa/fake:handle:alice">bridged</a> from <a href="web:fake:alice">fake:handle:alice</a> on fake-phrase by <a href="https://fed.brid.gy/">Bridgy Fed</a>]',
 | |
|         }, OtherFake.convert(Object(
 | |
|             id='fake:profile:alice', source_protocol='fake', our_as1={
 | |
|                 'objectType': 'person',
 | |
|                 'id': 'fake:alice',
 | |
|                 'url': 'http://unused',
 | |
|                 'summary': 'something about me',
 | |
|             }), from_user=alice))
 | |
| 
 | |
|     def test_convert_object_isnt_from_user_adds_source_links(self):
 | |
|         bob = Fake(id='fake:bob')
 | |
|         self.assertEqual({
 | |
|             'objectType': 'person',
 | |
|             'id': 'other:u:fake:alice',
 | |
|             'url': 'http://al/ice',
 | |
|             'summary': '[bridged from <a href="http://al/ice">al/ice</a> on fake-phrase by <a href="https://fed.brid.gy/">Bridgy Fed</a>]',
 | |
|         }, OtherFake.convert(Object(id='fake:alice', source_protocol='fake', our_as1={
 | |
|             'objectType': 'person',
 | |
|             'id': 'fake:alice',
 | |
|             'url': 'http://al/ice',
 | |
|         }), from_user=bob))
 | |
| 
 | |
|     def test_convert_actor_without_from_user_doesnt_add_source_links(self):
 | |
|         self.assertEqual({
 | |
|             'objectType': 'person',
 | |
|             'id': 'other:u:fake:alice',
 | |
|             'url': 'http://al/ice',
 | |
|         }, OtherFake.convert(Object(id='fake:alice', source_protocol='fake', our_as1={
 | |
|             'objectType': 'person',
 | |
|             'id': 'fake:alice',
 | |
|             'url': 'http://al/ice',
 | |
|         })))
 | |
| 
 | |
|     def test_convert_doesnt_duplicate_source_links(self):
 | |
|         alice = Fake(id='fake:alice')
 | |
|         summary = 'something about me<br><br>[bridged from <a href="http://al/ice">someone else</a> by <a href="https://fed.brid.gy/">Bridgy Fed</a>]'
 | |
|         self.assertEqual({
 | |
|             'objectType': 'person',
 | |
|             'id': 'other:u:fake:alice',
 | |
|             'summary': summary,
 | |
|         }, OtherFake.convert(Object(id='fake:alice', source_protocol='fake', our_as1={
 | |
|             'objectType': 'person',
 | |
|             'id': 'fake:alice',
 | |
|             'summary': summary,
 | |
|         }), from_user=alice))
 | |
| 
 | |
|     def test_convert_object_adds_source_links_to_create_update(self):
 | |
|         alice = Fake(id='fake:alice')
 | |
|         for verb in 'post', 'update':
 | |
|             self.assertEqual({
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': verb,
 | |
|                 'id': 'other:o:fa:fake:profile:update',
 | |
|                 'object': {
 | |
|                     'objectType': 'person',
 | |
|                     'id': 'other:u:fake:profile:alice',
 | |
|                     'summary': 'something about me<br><br>[<a href="https://fed.brid.gy/fa/fake:handle:alice">bridged</a> from <a href="web:fake:alice">fake:handle:alice</a> on fake-phrase by <a href="https://fed.brid.gy/">Bridgy Fed</a>]',
 | |
|                 },
 | |
|             }, OtherFake.convert(
 | |
|                 Object(id='fake:profile:update', source_protocol='fake', our_as1={
 | |
|                     'objectType': 'activity',
 | |
|                     'verb': verb,
 | |
|                     'object': {
 | |
|                         'id': 'fake:profile:alice',
 | |
|                         'objectType': 'person',
 | |
|                         'summary': 'something about me',
 | |
|                     },
 | |
|                 }), from_user=alice))
 | |
| 
 | |
|     def test_check_supported(self):
 | |
|         for obj in (
 | |
|             {'objectType': 'note'},
 | |
|             {'objectType': 'activity', 'verb': 'post',
 | |
|              'object': {'objectType': 'note'}},
 | |
|             {'objectType': 'activity', 'verb': 'follow'},
 | |
|             {'objectType': 'activity', 'verb': 'delete', 'object': 'x'},
 | |
|             {'objectType': 'activity', 'verb': 'undo', 'object': {'foo': 'bar'}},
 | |
|             {'objectType': 'activity', 'verb': 'undo',
 | |
|              'object': {'objectType': 'activity', 'verb': 'share'}},
 | |
|             {'objectType': 'activity', 'verb': 'flag'},
 | |
|         ):
 | |
|             with self.subTest(obj=obj):
 | |
|                 Fake.check_supported(Object(our_as1=obj))
 | |
| 
 | |
|         for obj in (
 | |
|             {'objectType': 'event'},
 | |
|             {'objectType': 'activity', 'verb': 'post',
 | |
|              'object': {'objectType': 'event'}},
 | |
|         ):
 | |
|             with self.subTest(obj=obj), self.assertRaises(NoContent):
 | |
|                 Fake.check_supported(Object(our_as1=obj))
 | |
| 
 | |
|         # Fake doesn't support DMs, ExplicitFake does
 | |
|         for author, recip in (
 | |
|                 ('ap.brid.gy', 'did:bob'),
 | |
|                 ('did:bob', 'ap.brid.gy'),
 | |
|         ):
 | |
|             bot_dm = Object(our_as1={
 | |
|                 'objectType': 'note',
 | |
|                 'author': author,
 | |
|                 'to': [recip],
 | |
|                 'content': 'hello world',
 | |
|             })
 | |
|             ExplicitFake.check_supported(bot_dm)
 | |
|             with self.assertRaises(NoContent):
 | |
|                 Fake.check_supported(bot_dm)
 | |
| 
 | |
|         # not from or to a protocol bot user
 | |
|         dm = Object(our_as1={
 | |
|             'objectType': 'note',
 | |
|             'author': 'did:alice',
 | |
|             'to': ['did:bob'],
 | |
|             'content': 'hello world',
 | |
|         })
 | |
|         for proto in Fake, ExplicitFake:
 | |
|             with self.subTest(proto=proto), self.assertRaises(NoContent):
 | |
|                 proto.check_supported(dm)
 | |
| 
 | |
|         # from and to a copy id of a protocol bot user
 | |
|         self.make_user(cls=Web, id='ap.brid.gy',
 | |
|                        copies=[Target(protocol='fake', uri='fake:ap-bot')])
 | |
|         common.protocol_user_copy_ids.cache_clear()
 | |
|         dm.our_as1['author'] = 'fake:ap-bot'
 | |
|         proto.check_supported(dm)
 | |
| 
 | |
|         dm.our_as1.update({
 | |
|             'author': 'did:alice',
 | |
|             'to': ['fake:ap-bot'],
 | |
|         })
 | |
|         proto.check_supported(dm)
 | |
| 
 | |
|     def test_bot_follow(self):
 | |
|         self.make_user(id='fa.brid.gy', cls=Web)
 | |
|         user = self.make_user(id='fake:user', cls=Fake, obj_id='fake:user')
 | |
|         Fake.bot_follow(user)
 | |
| 
 | |
|         self.assertEqual([('fake:user:target', {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'id': 'https://fa.brid.gy/#follow-back-fake:user-2022-01-02T03:04:05+00:00',
 | |
|             'actor': 'fa.brid.gy',
 | |
|             'object': 'fake:user',
 | |
|         })], Fake.sent)
 | |
| 
 | |
|     def test_bot_follow_user_missing_obj(self):
 | |
|         self.make_user(id='fa.brid.gy', cls=Web)
 | |
|         user = Fake(id='fake:user')
 | |
|         assert not user.obj
 | |
|         Fake.bot_follow(user)
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
| 
 | |
| class ProtocolReceiveTest(TestCase):
 | |
| 
 | |
|     def setUp(self):
 | |
|         super().setUp()
 | |
|         self.user = self.make_user('fake:user', cls=Fake, obj_id='fake:user')
 | |
|         self.alice = self.make_user('other:alice', cls=OtherFake, obj_id='other:alice')
 | |
|         self.bob = self.make_user('other:bob', cls=OtherFake, obj_id='other:bob')
 | |
| 
 | |
|     def assert_object(self, id, **props):
 | |
|         props.setdefault('source_protocol', 'fake')
 | |
|         return super().assert_object(id, **props)
 | |
| 
 | |
|     def make_followers(self):
 | |
|         Follower.get_or_create(to=self.user, from_=self.alice)
 | |
|         Follower.get_or_create(to=self.user, from_=self.bob)
 | |
|         Follower.get_or_create(to=self.user, from_=OtherFake(id='other:eve'),
 | |
|                                status='inactive')
 | |
| 
 | |
|     def test_create_post(self):
 | |
|         self.make_followers()
 | |
| 
 | |
|         post_as1 = {
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|         }
 | |
|         create_as1 = {
 | |
|             'id': 'fake:create',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'actor': 'fake:user',
 | |
|             'object': post_as1,
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(create_as1))
 | |
| 
 | |
|         self.assert_object('fake:post',
 | |
|                            our_as1=post_as1,
 | |
|                            type='note',
 | |
|                            copies=[Target(protocol='other',
 | |
|                                           uri='other:o:fa:fake:post')],
 | |
|                            feed=[self.alice.key, self.bob.key],
 | |
|                            users=[self.user.key],
 | |
|                            )
 | |
|         self.assertIsNone(Object.get_by_id('fake:create'))
 | |
|         self.assertEqual([
 | |
|             ('other:alice:target', create_as1),
 | |
|             ('other:bob:target', create_as1),
 | |
|         ], OtherFake.sent)
 | |
| 
 | |
|     def test_create_post_object_missing_id(self):
 | |
|         self.make_followers()
 | |
| 
 | |
|         # got an activity like this from Pandacap
 | |
|         # https://github.com/IsaacSchemm/Pandacap
 | |
|         create_as1 = {
 | |
|             'id': 'fake:create',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'actor': 'fake:user',
 | |
|             'object': [{
 | |
|                 'Item1': 'id',
 | |
|                 'Item2': 'https://pandacap.azurewebsites.net/#transient-abc-123',
 | |
|             }],
 | |
|         }
 | |
|         with self.assertRaises(ErrorButDoNotRetryTask):
 | |
|             Fake.receive_as1(create_as1)
 | |
| 
 | |
|         self.assertEqual([], OtherFake.sent)
 | |
| 
 | |
|     def test_create_post_bare_object(self):
 | |
|         self.make_followers()
 | |
| 
 | |
|         post_as1 = {
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(post_as1))
 | |
| 
 | |
|         self.assert_object(
 | |
|             'fake:post',
 | |
|             our_as1=post_as1,
 | |
|             type='note',
 | |
|             copies=[Target(protocol='other', uri='other:o:fa:fake:post')],
 | |
|             feed=[self.alice.key, self.bob.key],
 | |
|             users=[Fake(id='fake:user').key],
 | |
|         )
 | |
| 
 | |
|         self.assertIsNone(Object.get_by_id('fake:post#bridgy-fed-create'))
 | |
|         self.assertEqual('other:alice:target', OtherFake.sent[0][0])
 | |
|         self.assertEqual('other:bob:target', OtherFake.sent[1][0])
 | |
|         self.assertEqual('fake:post#bridgy-fed-create', OtherFake.sent[0][1]['id'])
 | |
|         self.assertEqual('fake:post#bridgy-fed-create', OtherFake.sent[1][1]['id'])
 | |
| 
 | |
|     @patch.object(ATProto, 'send', return_value=True)
 | |
|     def test_post_by_user_enabled_atproto_adds_pds_target(self, mock_send):
 | |
|         self.user.enabled_protocols = ['atproto']
 | |
|         self.user.put()
 | |
| 
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1({
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|         }))
 | |
| 
 | |
|         self.assertEqual(1, mock_send.call_count)
 | |
|         [obj, url], _ = mock_send.call_args
 | |
|         self.assertEqual('fake:post#bridgy-fed-create', obj.key.id())
 | |
|         self.assertEqual(ATProto.PDS_URL, url)
 | |
| 
 | |
|     def test_post_not_public_ignored(self):
 | |
|         self.assertEqual(('OK', 200), Fake.receive_as1({
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|             'to': ['fake:user/followers'],
 | |
|         }))
 | |
|         self.assertIsNone(Object.get_by_id('fake:post'))
 | |
| 
 | |
|     def test_post_unlisted_ignored(self):
 | |
|         self.assertEqual(('OK', 200), Fake.receive_as1({
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|             'to': ['@unlisted'],
 | |
|         }))
 | |
|         self.assertIsNone(Object.get_by_id('fake:post'))
 | |
| 
 | |
|     @patch.object(ATProto, 'send')
 | |
|     def test_reply_to_not_bridged_account_skips_atproto(self, mock_send):
 | |
|         user = self.make_user('efake:user', cls=ExplicitFake,
 | |
|                               enabled_protocols=['atproto'])
 | |
| 
 | |
|         self.eve = self.make_user('efake:eve', cls=ExplicitFake)
 | |
|         self.store_object(id='efake:post', our_as1={
 | |
|             'id': 'efake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'efake:eve',
 | |
|         })
 | |
| 
 | |
|         ExplicitFake.receive_as1({
 | |
|             'id': 'efake:reply',
 | |
|             'objectType': 'note',
 | |
|             'author': 'efake:user',
 | |
|             'inReplyTo': 'efake:post',
 | |
|         })
 | |
| 
 | |
|         self.assertEqual(0, mock_send.call_count)
 | |
| 
 | |
|     @patch.object(ATProto, 'send')
 | |
|     def test_reply_to_non_bridged_post_with_mention_skips_atproto(self, mock_send):
 | |
|         self.user.enabled_protocols = ['atproto']
 | |
|         self.user.put()
 | |
| 
 | |
|         self.store_object(id='fake:post', our_as1={
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'other:alice',
 | |
|         })
 | |
| 
 | |
|         Fake.receive_as1({
 | |
|             'id': 'fake:reply',
 | |
|             'objectType': 'note',
 | |
|             'actor': 'fake:user',
 | |
|             'inReplyTo': 'fake:post',
 | |
|             'tags': [{
 | |
|                 'objectType': 'mention',
 | |
|                 'url': 'other:bob'
 | |
|             }],
 | |
|         })
 | |
| 
 | |
|         self.assertEqual(0, mock_send.call_count)
 | |
| 
 | |
|     def test_reply_to_non_bridged_post_skips_enabled_protocol_with_followers(self):
 | |
|         self.make_user(id='fa.brid.gy', cls=Web)
 | |
| 
 | |
|         # should skip even if it's enabled and we have followers there
 | |
|         self.user.enabled_protocols = ['efake']
 | |
|         self.user.put()
 | |
| 
 | |
|         eve = self.make_user('efake:eve', cls=ExplicitFake)
 | |
|         Follower.get_or_create(from_=eve, to=self.user)
 | |
| 
 | |
|         self.store_object(id='fake:post', source_protocol='fake', our_as1={
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:alice',
 | |
|         })
 | |
|         _, code = Fake.receive_as1({
 | |
|             'id': 'fake:reply',
 | |
|             'objectType': 'note',
 | |
|             'actor': 'fake:user',
 | |
|             'inReplyTo': 'fake:post',
 | |
|         })
 | |
|         self.assertEqual(204, code)
 | |
|         self.assertEqual([], ExplicitFake.sent)
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     def test_reply_from_non_bridged_post_isnt_bridged_but_gets_dm_prompt(self):
 | |
|         self.make_user(id='fa.brid.gy', cls=Web)
 | |
|         self.user.enabled_protocols = ['efake']
 | |
|         self.user.put()
 | |
| 
 | |
|         eve = self.make_user('efake:eve', cls=ExplicitFake, obj_as1={
 | |
|             'id': 'efake:eve',
 | |
|         })
 | |
| 
 | |
|         self.store_object(id='fake:post', source_protocol='fake', our_as1={
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|         })
 | |
| 
 | |
|         _, code = ExplicitFake.receive_as1({
 | |
|             'id': 'efake:reply',
 | |
|             'objectType': 'note',
 | |
|             'actor': 'efake:eve',
 | |
|             'inReplyTo': 'fake:post',
 | |
|         })
 | |
|         self.assertEqual(204, code)
 | |
| 
 | |
|         self.assertEqual([], Fake.sent)
 | |
|         DmsTest().assert_sent(Fake, eve, 'replied_to_bridged_user', """Hi! You <a href="efake:reply">recently replied</a> to <a class="h-card u-author" href="fake:user">fake:user</a>, who's bridged here from fake-phrase. If you want them to see your replies, you can bridge your account into fake-phrase by following this account. <a href="https://fed.brid.gy/docs">See the docs</a> for more information.""")
 | |
| 
 | |
|         eve = eve.key.get()
 | |
|         self.assertEqual([DM(protocol='fake', type='replied_to_bridged_user')],
 | |
|                          eve.sent_dms)
 | |
| 
 | |
|     @patch.object(ATProto, 'send', return_value=True)
 | |
|     def test_repost_of_non_bridged_account_skips_atproto(self, mock_send):
 | |
|         user = self.make_user('efake:user', cls=ExplicitFake,
 | |
|                               enabled_protocols=['atproto'])
 | |
| 
 | |
|         self.eve = self.make_user('efake:eve', cls=ExplicitFake)
 | |
|         self.store_object(id='efake:post', our_as1={
 | |
|             'id': 'efake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'efake:eve',
 | |
|         })
 | |
| 
 | |
|         _, code = ExplicitFake.receive_as1({
 | |
|             'id': 'efake:repost',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'share',
 | |
|             'actor': 'efake:user',
 | |
|             'object': 'efake:post',
 | |
|         })
 | |
|         self.assertEqual(204, code)
 | |
|         self.assertEqual(0, mock_send.call_count)
 | |
| 
 | |
|     @patch.object(ATProto, 'send', return_value=True)
 | |
|     def test_repost_of_not_bridged_post_skips_atproto(self, mock_send):
 | |
|         user = self.make_user('efake:user', cls=ExplicitFake,
 | |
|                               enabled_protocols=['atproto'])
 | |
| 
 | |
|         self.eve = self.make_user('efake:eve', cls=ExplicitFake,
 | |
|                               enabled_protocols=['atproto'])
 | |
|         self.store_object(id='efake:post', our_as1={
 | |
|             'id': 'efake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'efake:eve',
 | |
|         })
 | |
| 
 | |
|         _, code = ExplicitFake.receive_as1({
 | |
|             'id': 'efake:repost',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'share',
 | |
|             'actor': 'efake:user',
 | |
|             'object': 'efake:post',
 | |
|         })
 | |
|         self.assertEqual(204, code)
 | |
|         self.assertEqual(0, mock_send.call_count)
 | |
| 
 | |
|     def test_repost_of_not_bridged_post_skips_enabled_protocol_with_followers(self):
 | |
|         # should skip even if it's enabled and we have followers there
 | |
|         self.user.enabled_protocols = ['efake']
 | |
|         self.user.put()
 | |
| 
 | |
|         eve = self.make_user('efake:eve', cls=ExplicitFake)
 | |
|         Follower.get_or_create(from_=eve, to=self.user)
 | |
| 
 | |
|         self.store_object(id='fake:post', source_protocol='fake', our_as1={
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:alice',
 | |
|         })
 | |
|         _, code = Fake.receive_as1({
 | |
|             'id': 'fake:repost',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'share',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'fake:post',
 | |
|         })
 | |
|         self.assertEqual(204, code)
 | |
|         self.assertEqual([], ExplicitFake.sent)
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     def test_undo_repost(self):
 | |
|         self.make_followers()
 | |
| 
 | |
|         self.store_object(id='other:orig', source_protocol='other', our_as1={
 | |
|             'objectType': 'note',
 | |
|             'id': 'other:orig',
 | |
|             'actor': 'other:user',
 | |
|         })
 | |
|         self.store_object(id='fake:share', source_protocol='fake', our_as1={
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'share',
 | |
|             'id': 'fake:share',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'other:orig',
 | |
|         })
 | |
| 
 | |
|         undo_as1 = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'undo',
 | |
|             'id': 'fake:undo',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'fake:share',
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(undo_as1))
 | |
|         self.assertTrue(Object.get_by_id('fake:share').deleted)
 | |
|         self.assertEqual([
 | |
|             ('other:alice:target', undo_as1),
 | |
|             ('other:bob:target', undo_as1),
 | |
|             ('other:orig:target', undo_as1),
 | |
|         ], OtherFake.sent)
 | |
| 
 | |
|     @patch.object(ATProto, 'send', return_value=True)
 | |
|     def test_follow_of_bridged_account_by_not_bridged_account_skips_atproto(
 | |
|             self, mock_send):
 | |
|         user = self.make_user('efake:user', cls=ExplicitFake)
 | |
|         self.store_object(id='did:plc:eve', raw=DID_DOC)
 | |
|         eve = self.make_user('did:plc:eve', cls=ATProto, enabled_protocols=['efake'],
 | |
|                              copies=[Target(uri='efake:eve', protocol='efake')],
 | |
|                              obj_bsky=ACTOR_PROFILE_BSKY)
 | |
| 
 | |
|         _, code = ExplicitFake.receive_as1({
 | |
|             'id': 'efake:follow',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'actor': 'efake:user',
 | |
|             'object': 'efake:eve',
 | |
|         })
 | |
|         self.assertEqual(204, code)
 | |
| 
 | |
|         self.assert_entities_equal(Follower(from_=user.key, to=eve.key,
 | |
|                                             follow=Object(id='efake:follow').key),
 | |
|                                    Follower.query().fetch(),
 | |
|                                    ignore=['created', 'updated'])
 | |
|         self.assertEqual(0, mock_send.call_count)
 | |
| 
 | |
|     def test_targets_block(self):
 | |
|         self.bob.obj.our_as1 = {'foo': 'bar'}
 | |
|         self.bob.obj.put()
 | |
| 
 | |
|         block = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'block',
 | |
|             'id': 'other:block',
 | |
|             'actor': 'other:alice',
 | |
|             'object': 'other:bob',
 | |
|         }
 | |
|         self.assertEqual(
 | |
|             [Target(uri='other:bob:target', protocol='other')],
 | |
|             list(Fake.targets(Object(our_as1=block), from_user=self.user).keys()))
 | |
| 
 | |
|     def test_targets_undo_composite_block(self):
 | |
|         self.bob.obj.our_as1 = {'foo': 'bar'}
 | |
|         self.bob.obj.put()
 | |
| 
 | |
|         obj = Object(id='other:undo', our_as1={
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'undo',
 | |
|             'id': 'other:undo',
 | |
|             'actor': 'fake:user',
 | |
|             'object': {
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'block',
 | |
|                 'id': 'other:block',
 | |
|                 'actor': 'fake:user',
 | |
|                 'object': 'other:bob',
 | |
|             },
 | |
|         })
 | |
|         self.assertEqual(
 | |
|             [Target(uri='other:bob:target', protocol='other')],
 | |
|             list(Fake.targets(obj, from_user=self.user).keys()))
 | |
| 
 | |
|     def test_targets_undo_block_id(self):
 | |
|         self.bob.obj.our_as1 = {'foo': 'bar'}
 | |
|         self.bob.obj.put()
 | |
| 
 | |
|         self.store_object(id='fake:block', our_as1={
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'block',
 | |
|             'id': 'fake:block',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'other:bob',
 | |
|         })
 | |
| 
 | |
|         obj = Object(id='fake:undo', our_as1={
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'undo',
 | |
|             'id': 'fake:undo',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'fake:block',
 | |
|         })
 | |
|         self.assertEqual(
 | |
|             [Target(uri='other:bob:target', protocol='other')],
 | |
|             list(Fake.targets(obj, from_user=self.user).keys()))
 | |
| 
 | |
|     def test_targets_undo_share_composite(self):
 | |
|         self.make_followers()
 | |
| 
 | |
|         OtherFake.fetchable['other:orig'] = {
 | |
|             'objectType': 'note',
 | |
|             'id': 'other:orig',
 | |
|         }
 | |
| 
 | |
|         share = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'share',
 | |
|             'id': 'fake:share',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'other:orig',
 | |
|         }
 | |
|         Fake.fetchable['fake:share'] = share
 | |
| 
 | |
|         obj = Object(id='fake:undo', our_as1={
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'undo',
 | |
|             'actor': 'fake:user',
 | |
|             'object': share,
 | |
|         })
 | |
|         self.assertEqual({
 | |
|             Target(protocol='other', uri='other:alice:target'),
 | |
|             Target(protocol='other', uri='other:bob:target'),
 | |
|             Target(protocol='other', uri='other:orig:target'),
 | |
|         }, Fake.targets(obj, from_user=self.user).keys())
 | |
| 
 | |
|     def test_targets_repost_of_quote_with_article_tag_uses_quote_post_as_orig_obj(self):
 | |
|         """https://github.com/snarfed/bridgy-fed/issues/1357"""
 | |
|         self.make_followers()
 | |
| 
 | |
|         self.user.enabled_protocols=['web']
 | |
|         self.user.put()
 | |
| 
 | |
|         eve = self.make_user('eve.com', cls=Web)
 | |
|         web_link = self.store_object(id='http://eve.com/link', source_protocol='web',
 | |
|                                      our_as1={'foo': 'bar'})
 | |
| 
 | |
|         quote_as1 = {
 | |
|             'objectType': 'note',
 | |
|             'id': 'fake:quote',
 | |
|             'author': 'fake:user',
 | |
|             'content': 'foo bar baz',
 | |
|             'tags': [{
 | |
|                 'objectType': 'article',
 | |
|                 'url': 'http://eve.com/link',
 | |
|                 'displayName': 'bar',
 | |
|                 'startIndex': 4,
 | |
|                 'length': 3
 | |
|             }],
 | |
|             'attachments': [{
 | |
|                 'objectType': 'note',
 | |
|                 'id': 'fake:orig',
 | |
|                 'url': 'url:fake:orig',
 | |
|             }]
 | |
|         }
 | |
|         quote_obj = self.store_object(
 | |
|             id='fake:quote',
 | |
|             source_protocol='fake',
 | |
|             our_as1=quote_as1,
 | |
|             copies=[Target(protocol='other', uri='other:quote')])
 | |
| 
 | |
|         repost_as1 = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'share',
 | |
|             'id': 'fake:repost',
 | |
|             'actor': 'fake:user',
 | |
|             'object': quote_as1,
 | |
|         }
 | |
|         targets = Fake.targets(Object(id='fake:repost', our_as1=repost_as1),
 | |
|                                from_user=self.user)
 | |
|         self.assertEqual({
 | |
|             'other:quote:target': quote_obj.key.id(),
 | |
|             'other:alice:target': quote_obj.key.id(),
 | |
|             'other:bob:target': quote_obj.key.id(),
 | |
|             'http://eve.com/link': None,
 | |
|         }, {target.uri: obj.key.id() if obj else None for target, obj in targets.items()})
 | |
| 
 | |
|     @patch.object(ATProto, 'send', return_value=True)
 | |
|     def test_atproto_targets_normalize_pds_url(self, mock_send):
 | |
|         # we were over-normalizing our PDS URL https://atproto.brid.gy , adding
 | |
|         # a trailing slash, and then ending up with both versions in targets.
 | |
|         # https://github.com/snarfed/bridgy-fed/issues/1032
 | |
|         self.user.enabled_protocols = ['atproto']
 | |
| 
 | |
|         # atproto follower
 | |
|         self.store_object(id='did:plc:eve', raw={**DID_DOC, 'id': 'at://did:plc:eve'})
 | |
|         obj = self.store_object(id='at://did:plc:eve/app.bsky.actor.profile/self',
 | |
|                                 bsky=ACTOR_PROFILE_BSKY)
 | |
|         eve = self.make_user('did:plc:eve', cls=ATProto, obj_key=obj.key)
 | |
|         Follower.get_or_create(from_=eve, to=self.user)
 | |
| 
 | |
|         note = Object(our_as1={
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|         })
 | |
|         create = Object(id='fake:post', our_as1={
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'object': note.as1,
 | |
|         })
 | |
|         self.assertEqual({
 | |
|             Target(uri='https://atproto.brid.gy', protocol='atproto'): None,
 | |
|         }, Fake.targets(create, crud_obj=note, from_user=self.user))
 | |
| 
 | |
|     def test_create_post_dont_deliver_to_follower_if_protocol_isnt_enabled(self):
 | |
|         # user who hasn't enabled either Fake or OtherFake, so we shouldn't
 | |
|         # deliver to followers on those protocols
 | |
|         user = self.make_user('efake:user', cls=ExplicitFake,
 | |
|                               obj_id='efake:user')
 | |
|         frank = self.make_user('other:frank', cls=OtherFake, obj_id='other:frank')
 | |
|         Follower.get_or_create(to=user, from_=self.alice)
 | |
|         Follower.get_or_create(to=user, from_=frank)
 | |
| 
 | |
|         _, code = ExplicitFake.receive_as1({
 | |
|             'objectType': 'note',
 | |
|             'id': 'efake:post',
 | |
|             'author': 'efake:user',
 | |
|             'content': 'foo'
 | |
|         })
 | |
|         self.assertEqual(204, code)
 | |
| 
 | |
|         self.assertEqual([], Fake.sent)
 | |
|         self.assertEqual([], OtherFake.sent)
 | |
|         obj = Object.get_by_id('efake:post#bridgy-fed-create')
 | |
| 
 | |
|     def test_create_post_use_instead(self):
 | |
|         self.make_user('fake:not-this', cls=Fake, use_instead=self.user.key, obj_mf2={
 | |
|             'type': ['h-card'],
 | |
|             'properties': {
 | |
|                 # this is the key part to test; Object.as1 uses this as id
 | |
|                 'url': ['fake:user'],
 | |
|             },
 | |
|         })
 | |
|         self.make_followers()
 | |
| 
 | |
|         note_as1 = {
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         obj = self.store_object(id='fake:post', our_as1=note_as1,
 | |
|                                 source_protocol='fake')
 | |
| 
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(note_as1))
 | |
|         self.assertEqual(2, len(OtherFake.sent))
 | |
| 
 | |
|         post_as1 = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'id': 'fake:post#bridgy-fed-create',
 | |
|             'actor': 'fake:user',
 | |
|             'object': note_as1,
 | |
|             'published': '2022-01-02T03:04:05+00:00',
 | |
|         }
 | |
|         self.assertEqual([
 | |
|             ('other:alice:target', post_as1),
 | |
|             ('other:bob:target', post_as1),
 | |
|         ], OtherFake.sent)
 | |
| 
 | |
|     def test_update_post_wrong_actor_error(self):
 | |
|         post_as1 = {
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         self.store_object(id='fake:post', our_as1=post_as1, source_protocol='fake')
 | |
| 
 | |
|         with self.assertRaises(ErrorButDoNotRetryTask):
 | |
|             Fake.receive_as1({
 | |
|                 'id': 'fake:update',
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'update',
 | |
|                 'actor': 'fake:other',
 | |
|                 'object': post_as1,
 | |
|             }, authed_as='fake:eve')
 | |
| 
 | |
|     def test_update_post(self):
 | |
|         self.make_followers()
 | |
| 
 | |
|         post_as1 = {
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         self.store_object(id='fake:post', our_as1=post_as1, source_protocol='fake',
 | |
|                              copies=[Target(uri='other:post', protocol='other')])
 | |
| 
 | |
|         update_as1 = {
 | |
|             'id': 'fake:update',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'update',
 | |
|             'actor': 'fake:user',
 | |
|             'object': post_as1,
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(update_as1))
 | |
| 
 | |
|         self.assert_object('fake:post',
 | |
|                            our_as1=post_as1,
 | |
|                            type='note',
 | |
|                            feed=[self.alice.key, self.bob.key],
 | |
|                            users=[self.user.key],
 | |
|                            copies=[Target(uri='other:post', protocol='other')],
 | |
|                            )
 | |
|         self.assertIsNone(Object.get_by_id('fake:update'))
 | |
| 
 | |
|         self.assertEqual([
 | |
|             ('other:alice:target', update_as1),
 | |
|             ('other:bob:target', update_as1),
 | |
|         ], OtherFake.sent)
 | |
| 
 | |
|     def test_update_post_bare_object(self):
 | |
|         self.make_followers()
 | |
| 
 | |
|         post_as1 = {
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|             'content': 'first',
 | |
|         }
 | |
|         copy = Target(uri='other:post', protocol='other')
 | |
|         self.store_object(id='fake:post', our_as1=post_as1,
 | |
|                           source_protocol='fake',
 | |
|                           copies=[copy])
 | |
| 
 | |
|         post_as1['content'] = 'second'
 | |
|         _, code = Fake.receive_as1(post_as1, new=False, changed=True)
 | |
|         self.assertEqual(202, code)
 | |
| 
 | |
|         self.assert_object('fake:post',
 | |
|                            our_as1={
 | |
|                                **post_as1,
 | |
|                                'updated': '2022-01-02T03:04:05+00:00',
 | |
|                            },
 | |
|                            type='note',
 | |
|                            feed=[self.bob.key, self.alice.key],
 | |
|                            users=[self.user.key],
 | |
|                            copies=[copy],
 | |
|                            )
 | |
| 
 | |
|         self.assertIsNone(Object.get_by_id(
 | |
|             'fake:post#bridgy-fed-update-2022-01-02T03:04:05+00:00'))
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     def test_update_post_fetch_object(self):
 | |
|         post = {
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         Fake.fetchable['fake:post'] = post
 | |
| 
 | |
|         self.store_object(id='fake:post', source_protocol='fake')
 | |
| 
 | |
|         update = {
 | |
|             'id': 'fake:update',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'update',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'fake:post',
 | |
|         }
 | |
|         _, status = Fake.receive_as1(update)
 | |
|         self.assertEqual(204, status)
 | |
| 
 | |
|         self.assertEqual(['fake:profile:user', 'fake:post'], Fake.fetched)
 | |
|         self.assert_object('fake:post',
 | |
|                            our_as1=post,
 | |
|                            type='note',
 | |
|                            )
 | |
|         self.assertIsNone(Object.get_by_id('fake:update'))
 | |
| 
 | |
|     def test_update_post_fetch_object_fails(self):
 | |
|         self.store_object(id='fake:post', source_protocol='fake')
 | |
| 
 | |
|         update = {
 | |
|             'id': 'fake:update',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'update',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'fake:post',
 | |
|         }
 | |
|         with self.assertRaises(ErrorButDoNotRetryTask):
 | |
|             Fake.receive_as1(update)
 | |
| 
 | |
|         self.assertEqual(['fake:profile:user', 'fake:post'], Fake.fetched)
 | |
|         self.assertIsNone(Object.get_by_id('fake:update'))
 | |
| 
 | |
|     def test_create_reply(self):
 | |
|         eve = self.make_user('other:eve', cls=OtherFake, obj_id='other:eve')
 | |
|         frank = self.make_user('other:frank', cls=OtherFake, obj_id='other:frank')
 | |
|         Follower.get_or_create(to=self.user, from_=frank)
 | |
| 
 | |
|         OtherFake.fetchable['other:post'] = {
 | |
|             'objectType': 'note',
 | |
|             'author': 'other:eve',
 | |
|         }
 | |
|         reply_as1 = {
 | |
|             'id': 'fake:reply',
 | |
|             'objectType': 'note',
 | |
|             'inReplyTo': 'other:post',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         create_as1 = {
 | |
|             'id': 'fake:create',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'actor': 'fake:user',
 | |
|             'object': reply_as1,
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(create_as1))
 | |
| 
 | |
|         self.assert_object(
 | |
|             'fake:reply',
 | |
|             our_as1=reply_as1,
 | |
|             type='note',
 | |
|             users=[self.user.key],
 | |
|             copies=[Target(protocol='other', uri='other:o:fa:fake:reply')],
 | |
|             notify=[eve.key],
 | |
|         )
 | |
|         self.assertIsNone(Object.get_by_id('fake:create'))
 | |
|         # not a self reply, shouldn't deliver to follower frank
 | |
|         self.assertEqual([('other:post:target', create_as1)], OtherFake.sent)
 | |
| 
 | |
|     def test_create_reply_bare_object(self):
 | |
|         eve = self.make_user('other:eve', cls=OtherFake, obj_id='other:eve')
 | |
| 
 | |
|         reply_as1 = {
 | |
|             'id': 'fake:reply',
 | |
|             'objectType': 'note',
 | |
|             'inReplyTo': 'other:post',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         OtherFake.fetchable['other:post'] = {
 | |
|             'objectType': 'note',
 | |
|             'id': 'other:post',
 | |
|             'author': 'other:eve',
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(reply_as1))
 | |
| 
 | |
|         self.assert_object(
 | |
|             'fake:reply',
 | |
|             our_as1=reply_as1,
 | |
|             type='note',
 | |
|             users=[self.user.key],
 | |
|             notify=[eve.key],
 | |
|             copies=[Target(protocol='other', uri='other:o:fa:fake:reply')],
 | |
|         )
 | |
| 
 | |
|         create_as1 = {
 | |
|             'id': 'fake:reply#bridgy-fed-create',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'actor': 'fake:user',
 | |
|             'object': reply_as1,
 | |
|             'published': '2022-01-02T03:04:05+00:00',
 | |
|         }
 | |
|         self.assertIsNone(Object.get_by_id('fake:reply#bridgy-fed-create'))
 | |
|         self.assertEqual([('other:post:target', create_as1)], OtherFake.sent)
 | |
| 
 | |
|     def test_create_reply_to_self_delivers_to_followers(self):
 | |
|         eve = self.make_user('other:eve', cls=OtherFake, obj_id='other:eve')
 | |
|         Follower.get_or_create(to=self.user, from_=eve)
 | |
| 
 | |
|         reply_as1 = {
 | |
|             'id': 'fake:reply',
 | |
|             'objectType': 'note',
 | |
|             'inReplyTo': 'fake:post',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         self.store_object(id='fake:post', source_protocol='fake',
 | |
|                           copies=[Target(protocol='other', uri='other:post')],
 | |
|                           our_as1={
 | |
|                               'objectType': 'note',
 | |
|                               'id': 'fake:post',
 | |
|                               'author': 'fake:user',
 | |
|                           })
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(reply_as1))
 | |
| 
 | |
|         self.assert_object(
 | |
|             'fake:reply',
 | |
|             our_as1=reply_as1,
 | |
|             type='note',
 | |
|             feed=[eve.key],
 | |
|             users=[self.user.key],
 | |
|             copies=[Target(protocol='other', uri='other:o:fa:fake:reply')],
 | |
|         )
 | |
| 
 | |
|         self.assertIsNone(Object.get_by_id('fake:reply#bridgy-fed-create'))
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     def test_create_reply_to_other_protocol(self):
 | |
|         eve = self.make_user('fake:eve', cls=Fake, obj_id='fake:eve')
 | |
|         self.store_object(id='fake:post', source_protocol='fake',
 | |
|                           copies=[Target(protocol='other', uri='other:post')],
 | |
|                           our_as1={
 | |
|                               'objectType': 'note',
 | |
|                               'id': 'fake:post',
 | |
|                               'author': 'fake:eve',
 | |
|                           })
 | |
|         self.store_object(id='other:post', source_protocol='other',
 | |
|                           our_as1={
 | |
|                               'objectType': 'note',
 | |
|                               'id': 'other:post',
 | |
|                               'author': 'fake:eve',
 | |
|                           })
 | |
| 
 | |
|         reply_as1 = {
 | |
|             'id': 'fake:reply',
 | |
|             'objectType': 'note',
 | |
|             'inReplyTo': 'fake:post',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(reply_as1))
 | |
| 
 | |
|         copy = Target(protocol='other', uri='other:o:fa:fake:reply')
 | |
|         reply = self.assert_object('fake:reply', our_as1=reply_as1, type='note',
 | |
|                                    users=[self.user.key], copies=[copy])
 | |
|         self.assertEqual([('other:post:target', {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'id': 'fake:reply#bridgy-fed-create',
 | |
|             'published': '2022-01-02T03:04:05+00:00',
 | |
|             'actor': 'fake:user',
 | |
|             'object': reply_as1,
 | |
|         })], OtherFake.sent)
 | |
| 
 | |
|     def test_create_reply_with_copy_on_not_enabled_protocol(self):
 | |
|         self.store_object(id='fake:post', source_protocol='fake',
 | |
|                           copies=[Target(protocol='efake', uri='efake:post')],
 | |
|                           our_as1={
 | |
|                               'objectType': 'note',
 | |
|                               'id': 'fake:post',
 | |
|                               'author': 'fake:alice',
 | |
|                           })
 | |
| 
 | |
|         _, code = Fake.receive_as1({
 | |
|             'objectType': 'note',
 | |
|             'id': 'fake:reply',
 | |
|             'author': 'fake:user',
 | |
|             'inReplyTo': 'fake:post',
 | |
|             'content': 'foo',
 | |
|         })
 | |
|         self.assertEqual(204, code)
 | |
|         self.assertEqual([], ExplicitFake.sent)
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     def test_create_self_reply_to_same_protocol_bridge_if_original_is_bridged(self):
 | |
|         # use efake because Protocol.targets automatically adds fake and other
 | |
|         # to to_protocols.
 | |
|         # TODO: refactor tests to not do fake-to-fake delivery, then remove
 | |
|         # these special cases
 | |
|         user = self.make_user('efake:user', cls=ExplicitFake,
 | |
|                               obj_id='efake:user', enabled_protocols=['other'])
 | |
| 
 | |
|         # eve follows user
 | |
|         eve = self.make_user('other:eve', cls=OtherFake, obj_id='other:eve')
 | |
|         Follower.get_or_create(to=user, from_=eve)
 | |
| 
 | |
|         # user replies to themselves
 | |
|         self.store_object(id='efake:post', source_protocol='efake',
 | |
|                           copies=[Target(protocol='other', uri='other:post')],
 | |
|                           our_as1={
 | |
|                               'objectType': 'note',
 | |
|                               'id': 'efake:post',
 | |
|                               'author': 'efake:user',
 | |
|                           })
 | |
| 
 | |
|         reply_as1 = {
 | |
|             'id': 'efake:reply',
 | |
|             'objectType': 'note',
 | |
|             'inReplyTo': 'efake:post',
 | |
|             'author': 'efake:user',
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), ExplicitFake.receive_as1(reply_as1))
 | |
| 
 | |
|         copy = Target(protocol='other', uri='other:o:efake:efake:reply')
 | |
|         reply = self.assert_object('efake:reply',
 | |
|                                    type='note',
 | |
|                                    source_protocol='efake',
 | |
|                                    our_as1=reply_as1,
 | |
|                                    users=[user.key],
 | |
|                                    copies=[copy],
 | |
|                                    feed=[eve.key])
 | |
|         expected_create = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'id': 'efake:reply#bridgy-fed-create',
 | |
|             'published': '2022-01-02T03:04:05+00:00',
 | |
|             'actor': 'efake:user',
 | |
|             'object': reply_as1,
 | |
|         }
 | |
| 
 | |
|         self.assertEqual([('other:eve:target', expected_create),
 | |
|                           ('other:post:target', expected_create),
 | |
|                           ], OtherFake.sent)
 | |
| 
 | |
|     def test_update_reply(self):
 | |
|         eve = self.make_user('other:eve', cls=OtherFake, obj_id='other:eve')
 | |
| 
 | |
|         OtherFake.fetchable['other:post'] = {
 | |
|             'objectType': 'note',
 | |
|             'author': 'other:eve',
 | |
|         }
 | |
|         reply_as1 = {
 | |
|             'id': 'fake:reply',
 | |
|             'objectType': 'note',
 | |
|             'inReplyTo': 'other:post',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         self.store_object(id='fake:reply', our_as1=reply_as1, source_protocol='fake')
 | |
| 
 | |
|         update_as1 = {
 | |
|             'id': 'fake:update',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'update',
 | |
|             'actor': 'fake:user',
 | |
|             'object': reply_as1,
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(update_as1))
 | |
| 
 | |
|         self.assert_object('fake:reply',
 | |
|                            our_as1=reply_as1,
 | |
|                            type='note',
 | |
|                            users=[self.user.key],
 | |
|                            notify=[eve.key],
 | |
|                            )
 | |
|         self.assertIsNone(Object.get_by_id('fake:update'))
 | |
|         self.assertEqual([('other:post:target', update_as1)], OtherFake.sent)
 | |
| 
 | |
|     def test_repost(self):
 | |
|         self.make_followers()
 | |
| 
 | |
|         OtherFake.fetchable['other:post'] = {
 | |
|             'objectType': 'note',
 | |
|             'author': 'other:bob',
 | |
|         }
 | |
|         repost_as1 = {
 | |
|             'id': 'fake:repost',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'share',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'other:post',
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(repost_as1))
 | |
| 
 | |
|         obj = self.assert_object('fake:repost',
 | |
|                                  our_as1=repost_as1,
 | |
|                                  copies=[Target(protocol='other',
 | |
|                                                 uri='other:o:fa:fake:repost')],
 | |
|                                  type='share',
 | |
|                                  users=[self.user.key],
 | |
|                                  notify=[self.bob.key],
 | |
|                                  feed=[self.alice.key, self.bob.key],
 | |
|                                  )
 | |
|         self.assertEqual([
 | |
|             ('other:alice:target', obj.as1),
 | |
|             ('other:bob:target', obj.as1),
 | |
|             ('other:post:target', obj.as1),
 | |
|         ], OtherFake.sent)
 | |
| 
 | |
|     def test_repost_twitter_blocklisted(self):
 | |
|         """Reposts of non-fediverse (ie blocklisted) sites aren't yet supported."""
 | |
|         repost_as1 = {
 | |
|             'id': 'fake:repost',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'share',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'https://twitter.com/foo',
 | |
|         }
 | |
|         _, code = Fake.receive_as1(repost_as1)
 | |
|         self.assertEqual(204, code)
 | |
| 
 | |
|         obj = self.assert_object('fake:repost',
 | |
|                                  our_as1=repost_as1,
 | |
|                                  type='share',
 | |
|                                  users=[self.user.key],
 | |
|                                  )
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     def test_repost_no_object_error(self):
 | |
|         with self.assertRaises(ErrorButDoNotRetryTask):
 | |
|             Fake.receive_as1({
 | |
|                 'id': 'fake:share',
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'share',
 | |
|                 'actor': 'fake:user',
 | |
|                 'object': None,
 | |
|         })
 | |
| 
 | |
|     def test_like(self):
 | |
|         OtherFake.fetchable['other:post'] = {
 | |
|             'objectType': 'note',
 | |
|             'author': 'other:bob',
 | |
|         }
 | |
| 
 | |
|         like_as1 = {
 | |
|             'id': 'fake:like',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'like',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'other:post',
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(like_as1))
 | |
| 
 | |
|         like_obj = self.assert_object('fake:like',
 | |
|                                       our_as1=like_as1,
 | |
|                                       copies=[Target(protocol='other',
 | |
|                                                      uri='other:o:fa:fake:like')],
 | |
|                                       users=[self.user.key],
 | |
|                                       notify=[self.bob.key],
 | |
|                                       type='like',
 | |
|                                       )
 | |
| 
 | |
|         self.assertEqual([('other:post:target', like_obj.as1)], OtherFake.sent)
 | |
| 
 | |
|     def test_like_no_object_error(self):
 | |
|         with self.assertRaises(ErrorButDoNotRetryTask):
 | |
|             Fake.receive_as1({
 | |
|                 'id': 'fake:like',
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'like',
 | |
|                 'actor': 'fake:user',
 | |
|                 'object': None,
 | |
|         })
 | |
| 
 | |
|     def test_delete(self):
 | |
|         self.make_followers()
 | |
| 
 | |
|         post_as1 = {
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         self.store_object(id='fake:post', our_as1=post_as1, source_protocol='fake',
 | |
|                           copies=[Target(protocol='other', uri='other:post')])
 | |
| 
 | |
|         delete_as1 = {
 | |
|             'id': 'fake:delete',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'delete',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'fake:post',
 | |
|         }
 | |
|         self.assertEqual(('OK', 202),
 | |
|                          Fake.receive_as1(delete_as1, authed_as='fake:user'))
 | |
| 
 | |
|         self.assert_object('fake:post',
 | |
|                            our_as1=post_as1,
 | |
|                            deleted=True,
 | |
|                            source_protocol='fake',
 | |
|                            copies=[Target(protocol='other', uri='other:post')],
 | |
|                            )
 | |
| 
 | |
|         self.assertIsNone(Object.get_by_id('fake:delete'))
 | |
|         self.assertEqual('other:alice:target', OtherFake.sent[0][0])
 | |
|         self.assertEqual('other:bob:target', OtherFake.sent[1][0])
 | |
|         self.assertEqual('fake:delete', OtherFake.sent[0][1]['id'])
 | |
|         self.assertEqual('fake:delete', OtherFake.sent[1][1]['id'])
 | |
| 
 | |
|     def test_delete_doesnt_fetch_author(self):
 | |
|         self.user.obj_key.delete()
 | |
| 
 | |
|         delete_as1 = {
 | |
|             'id': 'fake:delete',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'delete',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'fake:post',
 | |
|         }
 | |
|         _, status = Fake.receive_as1(delete_as1, authed_as='fake:user')
 | |
|         self.assertEqual(204, status)
 | |
| 
 | |
|         self.assertIsNone(Object.get_by_id('fake:delete'))
 | |
|         self.assertEqual([], Fake.fetched)
 | |
| 
 | |
|     def test_delete_no_followers_no_stored_object(self):
 | |
|         delete_as1 = {
 | |
|             'id': 'fake:delete',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'delete',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'fake:post',
 | |
|         }
 | |
|         _, code = Fake.receive_as1(delete_as1)
 | |
|         self.assertEqual(204, code)
 | |
| 
 | |
|         self.assertIsNone(Object.get_by_id('fake:post'))
 | |
|         self.assertIsNone(Object.get_by_id('fake:delete'))
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     def test_delete_not_authed_as_object_owner(self):
 | |
|         self.make_followers()
 | |
| 
 | |
|         self.store_object(id='fake:post', source_protocol='fake', our_as1={
 | |
|             'objectType': 'note',
 | |
|             'id': 'fake:post',
 | |
|             'author': 'fake:user',
 | |
|         })
 | |
| 
 | |
|         with self.assertRaises(ErrorButDoNotRetryTask):
 | |
|             Fake.receive_as1({
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'delete',
 | |
|                 'id': 'fake:delete',
 | |
|                 'actor': 'fake:user',
 | |
|                 'object': 'fake:post',
 | |
|             }, authed_as='fake:eve')
 | |
| 
 | |
|         self.assertFalse(Object.get_by_id('fake:post').deleted)
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     def test_delete_actor(self):
 | |
|         self.alice.obj.copies = [Target(protocol='fake', uri='fa:profile:other:alice')]
 | |
|         self.alice.obj.put()
 | |
| 
 | |
|         follower = Follower.get_or_create(to=self.user, from_=self.alice)
 | |
|         followee = Follower.get_or_create(to=self.alice, from_=self.user)
 | |
|         other = Follower.get_or_create(to=self.user, from_=self.bob)
 | |
|         self.assertEqual(3, Follower.query().count())
 | |
| 
 | |
|         _, code = OtherFake.receive_as1({
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'delete',
 | |
|             'id': 'other:delete',
 | |
|             'actor': 'other:alice',
 | |
|             'object': 'other:alice',
 | |
|         })
 | |
|         self.assertEqual(202, code)
 | |
| 
 | |
|         self.assertEqual(3, Follower.query().count())
 | |
|         self.assertEqual('inactive', follower.key.get().status)
 | |
|         self.assertEqual('inactive', followee.key.get().status)
 | |
|         self.assertEqual('active', other.key.get().status)
 | |
|         self.assert_object('other:alice', deleted=True, source_protocol='other',
 | |
|                            ignore=['copies'])
 | |
| 
 | |
|     @patch.object(Fake, 'send')
 | |
|     def test_send_error(self, mock_send):
 | |
|         """Two targets. First send fails, second succeeds."""
 | |
|         self.make_followers()
 | |
| 
 | |
|         post_as1 = {
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|         }
 | |
|         create_as1 = {
 | |
|             'id': 'fake:create',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'actor': 'fake:user',
 | |
|             'object': post_as1,
 | |
|         }
 | |
| 
 | |
|         sent = []
 | |
|         def send(obj, url, from_user=None, orig_obj_id=None):
 | |
|             self.assertEqual(create_as1, obj.as1)
 | |
|             if not sent:
 | |
|                 self.assertEqual('other:alice:target', url)
 | |
|                 sent.append('fail')
 | |
|                 raise BadRequest()
 | |
|             else:
 | |
|                 self.assertEqual('other:bob:target', url)
 | |
|                 sent.append('sent')
 | |
|                 return True
 | |
| 
 | |
|         mock_send.side_effect = send
 | |
| 
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(create_as1))
 | |
| 
 | |
|         self.assert_object('fake:post',
 | |
|                            our_as1=post_as1,
 | |
|                            type='note',
 | |
|                            feed=[self.alice.key, self.bob.key],
 | |
|                            users=[self.user.key],
 | |
|                            )
 | |
|         self.assertIsNone(Object.get_by_id('fake:create'))
 | |
|         self.assertEqual(['fail', 'sent'], sent)
 | |
| 
 | |
|     def test_update_profile(self):
 | |
|         self.user.obj = self.store_object(
 | |
|             id='fake:profile:user',
 | |
|             copies = [Target(protocol='other', uri='other:profile:fake:user')])
 | |
|         self.user.put()
 | |
| 
 | |
|         self.make_followers()
 | |
| 
 | |
|         actor = {
 | |
|             'objectType': 'person',
 | |
|             'id': 'fake:user',
 | |
|             'displayName': 'Ms. ☕ Baz',
 | |
|             'urls': [{'displayName': 'Ms. ☕ Baz', 'value': 'https://user.com/'}],
 | |
|             'updated': '2022-01-02T03:04:05+00:00',
 | |
|         }
 | |
|         id = 'fake:user#update-2022-01-02T03:04:05+00:00'
 | |
|         update_as1 = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'update',
 | |
|             'id': id,
 | |
|             'actor': actor,
 | |
|             'object': {**actor, 'id': 'fake:profile:user'},
 | |
|         }
 | |
|         Fake.receive_as1(update_as1)
 | |
| 
 | |
|         # profile object
 | |
|         self.assert_object('fake:profile:user',
 | |
|                            our_as1=update_as1['object'],
 | |
|                            copies=self.user.obj.copies,
 | |
|                            users=[self.user.key],
 | |
|                            )
 | |
| 
 | |
|         # update activity
 | |
|         self.assertIsNone(Object.get_by_id(id))
 | |
|         self.assertEqual([
 | |
|             ('other:alice:target', update_as1),
 | |
|             ('other:bob:target', update_as1),
 | |
|         ], OtherFake.sent)
 | |
| 
 | |
|     def test_update_profile_bare_object(self):
 | |
|         profile = {
 | |
|             'objectType': 'person',
 | |
|             'id': 'other:alice',
 | |
|             'displayName': 'Ms. ☕ Baz',
 | |
|             'summary': 'first',
 | |
|         }
 | |
|         self.alice.obj = self.store_object(
 | |
|             id='other:alice',
 | |
|             copies = [Target(protocol='fake', uri='fake:profile:other:alice')])
 | |
|         self.alice.put()
 | |
| 
 | |
|         Follower.get_or_create(to=self.alice, from_=self.user)
 | |
| 
 | |
|         # unchanged from what's already in the datastore. we should send update
 | |
|         # anyway (instead of create) since it's an actor.
 | |
|         OtherFake.receive_as1(profile)
 | |
| 
 | |
|         # profile object
 | |
|         profile['updated'] = '2022-01-02T03:04:05+00:00'
 | |
|         self.assert_object('other:alice',
 | |
|                            our_as1=profile,
 | |
|                            users=[self.alice.key],
 | |
|                            copies=[Target(protocol='fake',
 | |
|                                           uri='fake:profile:other:alice')],
 | |
|                            source_protocol='other',
 | |
|                            )
 | |
|         self.assertEqual([('fake:shared:target', {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'update',
 | |
|             'id': 'other:alice#bridgy-fed-update-2022-01-02T03:04:05+00:00',
 | |
|             'actor': profile,
 | |
|             'object': profile,
 | |
|         })], Fake.sent)
 | |
| 
 | |
|     def test_update_profile_use_instead(self):
 | |
|         user_instead = self.make_user('fake:user-instead', cls=Fake,
 | |
|                                        use_instead=self.user.key)
 | |
| 
 | |
|         profile = {
 | |
|             'objectType': 'person',
 | |
|             'id': 'fake:profile:user-instead',
 | |
|             'foo': 'bar',
 | |
|         }
 | |
|         obj = Object(id='fake:profile:user', source_protocol='fake', our_as1=profile)
 | |
|         Fake.receive(obj)
 | |
| 
 | |
|         # profile object
 | |
|         self.assert_object('fake:profile:user',
 | |
|                            our_as1={
 | |
|                                **profile,
 | |
|                                'updated': '2022-01-02T03:04:05+00:00',
 | |
|                            },
 | |
|                            source_protocol='fake',
 | |
|                            users=[self.user.key],
 | |
|                            )
 | |
|         self.assertIsNone(Object.get_by_id(
 | |
|             'fake:profile:user#bridgy-fed-update-2022-01-02T03:04:05+00:00'))
 | |
| 
 | |
|     def test_mention_object(self, *mocks):
 | |
|         self.alice.obj.our_as1 = {'id': 'other:alice', 'objectType': 'person'}
 | |
|         self.alice.obj.put()
 | |
|         self.bob.obj.our_as1 = {'id': 'other:bob', 'objectType': 'person'}
 | |
|         self.bob.obj.put()
 | |
| 
 | |
|         mention_as1 = {
 | |
|             'objectType': 'note',
 | |
|             'id': 'fake:mention',
 | |
|             'author': 'fake:user',
 | |
|             'content': 'something',
 | |
|             'tags': [{
 | |
|                 'objectType': 'mention',
 | |
|                 'url': 'other:alice',
 | |
|             }, {
 | |
|                 'objectType': 'mention',
 | |
|                 'url': 'other:bob',
 | |
|             }],
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(mention_as1))
 | |
| 
 | |
|         self.assert_object('fake:mention',
 | |
|                            our_as1=mention_as1,
 | |
|                            type='note',
 | |
|                            users=[self.user.key],
 | |
|                            notify=[self.alice.key, self.bob.key],
 | |
|                            copies=[Target(protocol='other',
 | |
|                                           uri='other:o:fa:fake:mention')],
 | |
|                            )
 | |
| 
 | |
|         self.assertIsNone(Object.get_by_id('fake:post#bridgy-fed-create'))
 | |
|         self.assertEqual('other:alice:target', OtherFake.sent[0][0])
 | |
|         self.assertEqual('other:bob:target', OtherFake.sent[1][0])
 | |
|         self.assertEqual('fake:mention#bridgy-fed-create', OtherFake.sent[0][1]['id'])
 | |
|         self.assertEqual('fake:mention#bridgy-fed-create', OtherFake.sent[1][1]['id'])
 | |
| 
 | |
|     def test_follow(self):
 | |
|         self._test_follow()
 | |
| 
 | |
|     def test_follow_existing_inactive(self):
 | |
|         follower = Follower.get_or_create(to=self.alice, from_=self.user)
 | |
|         self._test_follow()
 | |
| 
 | |
|     def test_follow_actor_object_composite_objects(self):
 | |
|         self._test_follow(actor={'id': 'fake:user', 'objectType': 'person'},
 | |
|                           object={'id': 'other:alice', 'objectType': 'person'})
 | |
| 
 | |
|     def _test_follow(self, **extra):
 | |
|         OtherFake.fetchable['other:alice'] = {}
 | |
| 
 | |
|         follow_as1 = {
 | |
|             'id': 'fake:follow',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'other:alice',
 | |
|             **extra,
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(follow_as1))
 | |
| 
 | |
|         follow_obj = self.assert_object(
 | |
|             'fake:follow',
 | |
|             our_as1=follow_as1,
 | |
|             copies=[Target(protocol='other', uri='other:o:fa:fake:follow')],
 | |
|             users=[self.user.key],
 | |
|             notify=[self.alice.key],
 | |
|             feed=[],
 | |
|         )
 | |
| 
 | |
|         accept_id = 'other:alice/followers#accept-fake:follow'
 | |
|         accept_as1 = {
 | |
|             'id': accept_id,
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'accept',
 | |
|             'actor': 'other:alice',
 | |
|             'object': follow_as1,
 | |
|         }
 | |
|         self.assertEqual([('other:alice:target', follow_obj.as1)], OtherFake.sent)
 | |
|         self.assertEqual([('fake:user:target', accept_as1)], Fake.sent)
 | |
| 
 | |
|         self.assert_entities_equal(
 | |
|             Follower(to=self.alice.key, from_=self.user.key, status='active',
 | |
|                      follow=follow_obj.key),
 | |
|             Follower.query().fetch(),
 | |
|             ignore=['created', 'updated'],
 | |
|         )
 | |
| 
 | |
|     def test_follow_protocol_that_doesnt_support_accept(self, **extra):
 | |
|         OtherFake.fetchable['other:eve'] = {}
 | |
| 
 | |
|         follow_as1 = {
 | |
|             'id': 'other:follow',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'actor': 'other:eve',
 | |
|             'object': 'fake:user',
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), OtherFake.receive_as1(follow_as1))
 | |
| 
 | |
|         other = OtherFake.get_by_id('other:eve')
 | |
|         self.assert_object(
 | |
|             'other:follow',
 | |
|             source_protocol='other',
 | |
|             our_as1=follow_as1,
 | |
|             copies=[Target(protocol='fake', uri='fake:o:other:other:follow')],
 | |
|             users=[OtherFake(id='other:eve').key],
 | |
|             notify=[self.user.key],
 | |
|         )
 | |
| 
 | |
|         self.assertEqual(0, Object.query(Object.type == 'accept').count())
 | |
|         self.assertEqual([], OtherFake.sent)
 | |
| 
 | |
|     def test_follow_no_actor(self):
 | |
|         with self.assertRaises(ErrorButDoNotRetryTask):
 | |
|             Fake.receive_as1({
 | |
|                 'id': 'fake:follow',
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'follow',
 | |
|                 'object': 'fake:user',
 | |
|             })
 | |
| 
 | |
|         self.assertEqual([], Follower.query().fetch())
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     def test_follow_no_object(self):
 | |
|         with self.assertRaises(ErrorButDoNotRetryTask):
 | |
|             Fake.receive_as1({
 | |
|                 'id': 'other:follow',
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'follow',
 | |
|                 'actor': 'other:alice',
 | |
|             })
 | |
| 
 | |
|         self.assertEqual([], Follower.query().fetch())
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     def test_follow_object_unknown_protocol(self):
 | |
|         with self.assertRaises(ErrorButDoNotRetryTask):
 | |
|             Fake.receive_as1({
 | |
|                 'id': 'other:follow',
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'follow',
 | |
|                 'actor': 'other:alice',
 | |
|                 'object': 'unknown:bob',
 | |
|             })
 | |
| 
 | |
|         self.assertEqual([], Follower.query().fetch())
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     def test_accept_noop(self, **extra):
 | |
|         eve = self.make_user('other:eve', cls=OtherFake)
 | |
|         accept_as1 = {
 | |
|             'id': 'other:accept',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'accept',
 | |
|             'actor': 'other:eve',
 | |
|             'object': 'fake:follow'
 | |
|         }
 | |
| 
 | |
|         with self.assertRaises(NoContent):
 | |
|             _, status = OtherFake.receive_as1(accept_as1)
 | |
| 
 | |
|         self.assertEqual([], Fake.sent)
 | |
|         self.assertEqual([], OtherFake.sent)
 | |
| 
 | |
|     @patch('dms.maybe_send')
 | |
|     def test_follow_accept(self, _, **extra):
 | |
|         self.user.enable_protocol(ExplicitFake)
 | |
|         ExplicitFake.fetchable['efake:follow'] = {'id': 'efake:follow'}
 | |
|         accept_as1 = {
 | |
|             'id': 'fake:accept',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'accept',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'efake:follow'
 | |
|         }
 | |
| 
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(accept_as1))
 | |
|         self.assertEqual([('efake:follow:target', accept_as1)], ExplicitFake.sent)
 | |
| 
 | |
|     def test_stop_following(self):
 | |
|         self.user.obj.our_as1 = {'id': 'fake:user'}
 | |
|         self.user.obj.put()
 | |
| 
 | |
|         follower = Follower.get_or_create(to=self.user, from_=self.alice)
 | |
| 
 | |
|         stop_as1 = {
 | |
|             'id': 'other:stop-following',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'stop-following',
 | |
|             'actor': 'other:alice',
 | |
|             'object': 'fake:user',
 | |
|         }
 | |
|         _, code = OtherFake.receive_as1(stop_as1)
 | |
|         self.assertEqual(202, code)
 | |
| 
 | |
|         self.assertEqual('inactive', follower.key.get().status)
 | |
|         self.assertEqual([('fake:user:target', stop_as1)], Fake.sent)
 | |
| 
 | |
|     def test_stop_following_doesnt_exist(self):
 | |
|         self.user.obj.our_as1 = {'id': 'fake:user'}
 | |
|         self.user.obj.put()
 | |
| 
 | |
|         stop_following_as1 = {
 | |
|             'id': 'other:stop-following',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'stop-following',
 | |
|             'actor': 'other:alice',
 | |
|             'object': 'fake:user',
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), OtherFake.receive_as1(stop_following_as1))
 | |
| 
 | |
|         self.assertEqual(0, Follower.query().count())
 | |
|         self.assertEqual([('fake:user:target', stop_following_as1)], Fake.sent)
 | |
| 
 | |
|     def test_stop_following_inactive(self):
 | |
|         self.user.obj.our_as1 = {'id': 'fake:user'}
 | |
|         self.user.obj.put()
 | |
| 
 | |
|         follower = Follower.get_or_create(to=self.user, from_=self.alice,
 | |
|                                           status='inactive')
 | |
| 
 | |
|         stop_following_as1 = {
 | |
|             'id': 'other:stop-following',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'stop-following',
 | |
|             'actor': 'other:alice',
 | |
|             'object': 'fake:user',
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), OtherFake.receive_as1(stop_following_as1))
 | |
| 
 | |
|         self.assertEqual('inactive', follower.key.get().status)
 | |
|         self.assertEqual([('fake:user:target', stop_following_as1)], Fake.sent)
 | |
| 
 | |
|     def test_block(self):
 | |
|         self.bob.obj.our_as1 = {'id': 'other:bob'}
 | |
|         self.bob.obj.put()
 | |
| 
 | |
|         block_as1 = {
 | |
|             'id': 'fake:block',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'block',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'other:bob',
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(block_as1))
 | |
|         self.assertEqual([('other:bob:target', block_as1)], OtherFake.sent)
 | |
| 
 | |
|     def test_undo_block(self):
 | |
|         eve = self.make_user(id='other:eve', cls=OtherFake,
 | |
|                              obj_as1={'id': 'other:eve'})
 | |
|         self.make_followers()
 | |
| 
 | |
|         block = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'block',
 | |
|             'id': 'fake:block',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'other:eve',
 | |
|         }
 | |
|         self.store_object(id='fake:block', our_as1=block, source_protocol='fake')
 | |
| 
 | |
|         undo_as1 = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'undo',
 | |
|             'id': 'fake:undo',
 | |
|             'actor': 'fake:user',
 | |
|             'object': block,
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(undo_as1))
 | |
|         self.assertEqual([('other:eve:target', undo_as1)], OtherFake.sent)
 | |
| 
 | |
|     def test_undo_not_authed_as_object_owner(self):
 | |
|         self.store_object(id='fake:repost', source_protocol='fake', our_as1={
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'share',
 | |
|             'id': 'fake:repost',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'fake:post',
 | |
|         })
 | |
| 
 | |
|         with self.assertRaises(ErrorButDoNotRetryTask):
 | |
|             Fake.receive_as1({
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'undo',
 | |
|                 'id': 'fake:undo',
 | |
|                 'actor': 'fake:user',
 | |
|                 'object': 'fake:repost',
 | |
|             }, authed_as='fake:eve')
 | |
| 
 | |
|         self.assertFalse(Object.get_by_id('fake:repost').deleted)
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     @skip
 | |
|     def test_from_bridgy_fed_domain_fails(self):
 | |
|         with self.assertRaises(BadRequest):
 | |
|             Fake.receive_as1({
 | |
|                 'id': 'https://fed.brid.gy/r/foo',
 | |
|             })
 | |
| 
 | |
|         self.assertIsNone(Object.get_by_id('https://fed.brid.gy/r/foo'))
 | |
| 
 | |
|         with self.assertRaises(BadRequest):
 | |
|             Fake.receive_as1({
 | |
|                 'id': 'fake:foo',
 | |
|                 'actor': 'https://ap.brid.gy/user.com',
 | |
|             })
 | |
| 
 | |
|         self.assertIsNone(Object.get_by_id('foo'))
 | |
|         self.assertIsNone(Object.get_by_id('https://ap.brid.gy/user.com'))
 | |
| 
 | |
|     def test_skip_same_protocol(self):
 | |
|         self.make_user('other:carol', cls=OtherFake, obj_id='other:carol')
 | |
|         self.make_user('other:dan', cls=OtherFake, obj_id='other:dan')
 | |
| 
 | |
|         OtherFake.fetchable = {
 | |
|             'other:carol': {},
 | |
|         }
 | |
| 
 | |
|         follow_as1 = {
 | |
|             'id': 'other:follow',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'actor': 'other:carol',
 | |
|             'object': ['other:dan', 'fake:user'],
 | |
|         }
 | |
| 
 | |
|         self.assertEqual(('OK', 202), OtherFake.receive_as1(follow_as1))
 | |
| 
 | |
|         self.assertEqual(1, len(Fake.sent))
 | |
|         self.assertEqual('other:follow', Fake.sent[0][1]['id'])
 | |
| 
 | |
|         followers = Follower.query().fetch()
 | |
|         self.assertEqual(1, len(followers))
 | |
|         self.assertEqual(self.user.key, followers[0].to)
 | |
| 
 | |
|     def test_skip_bridged_user(self):
 | |
|         """If the actor isn't from the source protocol, skip the activity.
 | |
| 
 | |
|         (It's probably from a bridged user, and we only want to handle source
 | |
|         activities, not bridged activities.)
 | |
|         """
 | |
|         self.user.copies = [Target(uri='other:user', protocol='other')]
 | |
|         self.user.put()
 | |
| 
 | |
|         with self.assertRaises(NoContent):
 | |
|             OtherFake.receive_as1({
 | |
|                 'id': 'other:follow',
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'follow',
 | |
|                 'actor': 'fake:user',
 | |
|                 'object': 'other:alice',
 | |
|             })
 | |
|         self.assertEqual([], OtherFake.sent)
 | |
|         self.assertEqual([], Fake.sent)
 | |
|         self.assertIsNone(Object.get_by_id('other:follow'))
 | |
| 
 | |
|     @patch('requests.post')
 | |
|     @patch('requests.get')
 | |
|     def test_skip_web_same_domain(self, mock_get, mock_post):
 | |
|         self.make_user('user.com', cls=Web)
 | |
|         mock_get.side_effect = [
 | |
|             ACTOR_HTML_RESP,
 | |
|             NOTE_HTML_RESP,
 | |
|             NOTE_HTML_RESP,
 | |
|             NOTE_HTML_RESP,
 | |
|             NOTE_HTML_RESP,
 | |
|         ]
 | |
| 
 | |
|         follow_as1 = {
 | |
|             'id': 'http://user.com/follow',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'actor': 'http://user.com/',
 | |
|             'object': ['http://user.com/bob', 'http://user.com/eve'],
 | |
|         }
 | |
| 
 | |
|         _, code = Web.receive(Object(our_as1=follow_as1), authed_as='user.com')
 | |
|         self.assertEqual(204, code)
 | |
| 
 | |
|         mock_post.assert_not_called()
 | |
|         self.assertEqual(0, Follower.query().count())
 | |
| 
 | |
|     def test_opted_out(self):
 | |
|         self.user.obj.our_as1 = {
 | |
|             'id': 'fake:user',
 | |
|             'summary': '#nobridge',
 | |
|         }
 | |
|         self.user.obj.put()
 | |
| 
 | |
|         with self.assertRaises(NoContent):
 | |
|             Fake.receive_as1({
 | |
|                 'id': 'fake:post',
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'post',
 | |
|                 'actor': 'fake:user',
 | |
|                 'object': {
 | |
|                     'id': 'fake:note',
 | |
|                     'content': 'foo',
 | |
|                 },
 | |
|             })
 | |
| 
 | |
|     def test_activity_id_blocklisted(self):
 | |
|         with self.assertRaises(ErrorButDoNotRetryTask):
 | |
|             Fake.receive_as1({
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'delete',
 | |
|                 'id': 'fake:blocklisted:delete',
 | |
|                 'actor': 'fake:user',
 | |
|                 'object': 'fake:foo',
 | |
|             })
 | |
| 
 | |
|     def test_resolve_ids_follow(self):
 | |
|         follow = {
 | |
|             'id': 'fake:follow',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'fake:alice',
 | |
|         }
 | |
| 
 | |
|         # no matching copy users
 | |
|         obj = Object(id='fake:follow', our_as1=follow, source_protocol='fake')
 | |
|         _, code = Fake.receive(obj, authed_as='fake:user')
 | |
|         self.assertEqual(204, code)
 | |
|         self.assert_equals(follow, obj.our_as1)
 | |
| 
 | |
|         # matching copy user
 | |
|         self.alice.copies = [Target(uri='fake:alice', protocol='fake')]
 | |
|         self.alice.put()
 | |
| 
 | |
|         models.get_original_user_key.cache_clear()
 | |
|         models.get_original_object_key.cache_clear()
 | |
|         memcache.memcache.clear()
 | |
|         memcache.pickle_memcache.clear()
 | |
| 
 | |
|         obj.new = True
 | |
|         Fake.fetchable = {
 | |
|             'fake:alice': {},
 | |
|         }
 | |
| 
 | |
|         self.assertEqual(('OK', 202), Fake.receive(obj, authed_as='fake:user'))
 | |
|         self.assert_equals({
 | |
|             **follow,
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'other:alice',
 | |
|         }, Object.get_by_id('fake:follow').our_as1)
 | |
| 
 | |
|     def test_resolve_ids_share(self):
 | |
|         share = {
 | |
|             'objectType': 'activity',
 | |
|             'actor': 'fake:user',
 | |
|             'verb': 'share',
 | |
|             'object': 'fake:post',
 | |
|         }
 | |
| 
 | |
|         # no matching copy object
 | |
|         obj = Object(id='fake:share', our_as1=share, source_protocol='fake')
 | |
|         _, code = Fake.receive(obj, authed_as='fake:user')
 | |
|         self.assertEqual(204, code)
 | |
|         self.assert_equals(share, obj.our_as1)
 | |
| 
 | |
|         # matching copy object
 | |
|         self.store_object(id='other:post',
 | |
|                           copies=[Target(uri='fake:post', protocol='fake')])
 | |
| 
 | |
|         models.get_original_user_key.cache_clear()
 | |
|         models.get_original_object_key.cache_clear()
 | |
|         memcache.memcache.clear()
 | |
|         memcache.pickle_memcache.clear()
 | |
|         obj.new = True
 | |
| 
 | |
|         _, code = Fake.receive(obj, authed_as='fake:user')
 | |
|         self.assertEqual(204, code)
 | |
| 
 | |
|         self.assert_equals({
 | |
|             'id': 'fake:share',
 | |
|             'objectType': 'activity',
 | |
|             'actor': 'fake:user',
 | |
|             'verb': 'share',
 | |
|             'object': 'other:post',
 | |
|         }, obj.our_as1)
 | |
| 
 | |
|     def test_resolve_ids_reply_mentions(self):
 | |
|         reply = {
 | |
|             'id': 'fake:reply',
 | |
|             'author': 'fake:user',
 | |
|             'objectType': 'note',
 | |
|             'inReplyTo': [
 | |
|                 'fake:unknown-post',
 | |
|                 'fake:post',
 | |
|             ],
 | |
|             'tags': [{
 | |
|                 'objectType': 'mention',
 | |
|                 'url': 'fake:alice',
 | |
|             }, {
 | |
|                 'objectType': 'mention',
 | |
|                 'url': 'fake:bob',
 | |
|             }],
 | |
|         }
 | |
| 
 | |
|         # no matching copies
 | |
|         _, code = Fake.receive_as1(reply)
 | |
|         self.assertEqual(204, code)
 | |
|         self.assert_equals(reply, Object.get_by_id('fake:reply').our_as1)
 | |
| 
 | |
|         # matching copies
 | |
|         self.alice.copies=[Target(uri='fake:alice', protocol='fake')]
 | |
|         self.alice.put()
 | |
|         self.alice.obj = Object(id='other:alice', our_as1={'a': 'b'})
 | |
|         self.alice.obj.our_as1 = {'a': 'b'}
 | |
| 
 | |
|         self.bob.copies=[Target(uri='fake:bob', protocol='fake')]
 | |
|         self.bob.put()
 | |
|         self.bob.obj = Object(id='other:bob', our_as1={'x': 'y'})
 | |
|         self.bob.obj.our_as1 = {'a': 'b'}
 | |
|         self.bob.obj.put()
 | |
| 
 | |
|         models.get_original_user_key.cache_clear()
 | |
|         models.get_original_object_key.cache_clear()
 | |
|         memcache.pickle_memcache.clear()
 | |
| 
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(reply, new=True))
 | |
|         self.assertEqual({
 | |
|             'id': 'fake:reply',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|             'inReplyTo': [
 | |
|                 'fake:unknown-post',
 | |
|                 'fake:post',
 | |
|             ],
 | |
|             'tags': [{
 | |
|                 'objectType': 'mention',
 | |
|                 'url': 'other:alice',
 | |
|             }, {
 | |
|                 'objectType': 'mention',
 | |
|                 'url': 'other:bob',
 | |
|             }],
 | |
|         }, Object.get_by_id('fake:reply').our_as1)
 | |
| 
 | |
|     def test_follow_and_block_protocol_user_sets_enabled_protocols(self):
 | |
|         # bot user
 | |
|         self.make_user('fa.brid.gy', cls=Web)
 | |
| 
 | |
|         follow = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'id': 'efake:follow',
 | |
|             'actor': 'efake:user',
 | |
|             'object': 'fa.brid.gy',
 | |
|         }
 | |
|         block = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'block',
 | |
|             'id': 'efake:block',
 | |
|             'actor': 'efake:user',
 | |
|             'object': 'fa.brid.gy',
 | |
|         }
 | |
| 
 | |
|         user = self.make_user('efake:user', cls=ExplicitFake)
 | |
|         self.assertFalse(user.is_enabled(Fake))
 | |
|         ExplicitFake.fetchable = {'efake:user': {'profile': 'info'}}
 | |
| 
 | |
|         # fake protocol isn't enabled yet, block should be a noop
 | |
|         self.assertEqual(('OK', 200), ExplicitFake.receive_as1(block))
 | |
|         user = user.key.get()
 | |
|         self.assertEqual([], user.enabled_protocols)
 | |
|         self.assertEqual([], Fake.created_for)
 | |
| 
 | |
|         # follow should add to enabled_protocols
 | |
|         _, code = ExplicitFake.receive_as1(follow)
 | |
|         self.assertEqual(204, code)
 | |
|         user = user.key.get()
 | |
|         self.assertEqual({
 | |
|             'id': 'efake:user',
 | |
|             'profile': 'info',
 | |
|         }, user.obj.as1)
 | |
| 
 | |
|         self.assertEqual(['fake'], user.enabled_protocols)
 | |
|         self.assertEqual(['efake:user'], Fake.created_for)
 | |
|         self.assertTrue(user.is_enabled(Fake))
 | |
| 
 | |
|         dm_id = 'https://fa.brid.gy/#welcome-dm-efake:user-2022-01-02T03:04:05+00:00-create'
 | |
|         follow_back_id = 'https://fa.brid.gy/#follow-back-efake:user-2022-01-02T03:04:05+00:00'
 | |
| 
 | |
|         self.assertEqual([
 | |
|             # fa.brid.gy follows back
 | |
|             ('efake:user:target', {
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'follow',
 | |
|                 'id': 'https://fa.brid.gy/#follow-back-efake:user-2022-01-02T03:04:05+00:00',
 | |
|                 'actor': 'fa.brid.gy',
 | |
|                 'object': 'efake:user',
 | |
|             }),
 | |
|             # accept follow
 | |
|             ('efake:user:target', {
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'accept',
 | |
|                 'id': 'fa.brid.gy/followers#accept-efake:follow',
 | |
|                 'actor': 'fa.brid.gy',
 | |
|                 'object': {
 | |
|                     **follow,
 | |
|                     'actor': {'id': 'efake:user', 'profile': 'info'},
 | |
|                 },
 | |
|             }),
 | |
|         ], ExplicitFake.sent[1:])
 | |
| 
 | |
|         ExplicitFake.sent = ExplicitFake.sent[:1]
 | |
|         DmsTest().assert_sent(Fake, user, 'welcome', 'Welcome to Bridgy Fed! Your account will soon be bridged to fake-phrase at <a class="h-card u-author" rel="me" href="web:fake:efake:user" title="fake:handle:efake:handle:user">fake:handle:efake:handle:user</a>. <a href="https://fed.brid.gy/docs">See the docs</a> and <a href="https://fed.brid.gy/efake/efake:handle:user">your user page</a> for more information. To disable this and delete your bridged profile, block this account.')
 | |
| 
 | |
|         # another follow should be a noop
 | |
|         follow['id'] += '2'
 | |
|         Fake.created_for = []
 | |
|         _, code = ExplicitFake.receive_as1(follow)
 | |
|         self.assertEqual(204, code)
 | |
|         user = user.key.get()
 | |
|         self.assertEqual(['fake'], user.enabled_protocols)
 | |
|         self.assertTrue(user.is_enabled(Fake))
 | |
|         self.assertEqual(['efake:user'], Fake.created_for)
 | |
| 
 | |
|         # block should remove from enabled_protocols
 | |
|         Follower.get_or_create(to=user, from_=self.user)
 | |
|         block['id'] += '2'
 | |
|         self.assertEqual(('OK', 200), ExplicitFake.receive_as1(block))
 | |
|         user = user.key.get()
 | |
|         self.assertEqual([], user.enabled_protocols)
 | |
|         self.assertFalse(user.is_enabled(Fake))
 | |
| 
 | |
|         # ...and delete copy actor
 | |
|         id = 'efake:user#bridgy-fed-delete-user-fake-2022-01-02T03:04:05+00:00'
 | |
|         delete_efake = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'delete',
 | |
|             'id': id,
 | |
|             'actor': 'efake:user',
 | |
|             'object': 'efake:user',
 | |
|         }
 | |
|         self.assertEqual([('fake:shared:target', delete_efake)], Fake.sent)
 | |
|         self.assertIsNone(Object.get_by_id(id))
 | |
| 
 | |
|     def test_follow_bot_user_refreshes_profile(self):
 | |
|         # bot user
 | |
|         self.make_user('fa.brid.gy', cls=Web)
 | |
| 
 | |
|         # store profile that's opted out
 | |
|         user = self.make_user('efake:user', cls=ExplicitFake, obj_as1={
 | |
|             'id': 'efake:user',
 | |
|             'summary': '#nobridge',
 | |
|         })
 | |
|         self.assertFalse(user.is_enabled(Fake))
 | |
| 
 | |
|         # updated profile isn't opted out
 | |
|         ExplicitFake.fetchable = {'efake:user': {
 | |
|             'id': 'efake:user',
 | |
|             'summary': 'never mind',
 | |
|         }}
 | |
| 
 | |
|         # follow should refresh profile
 | |
|         _, code = ExplicitFake.receive_as1({
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'id': 'efake:follow',
 | |
|             'actor': 'efake:user',
 | |
|             'object': 'fa.brid.gy',
 | |
|         })
 | |
|         self.assertEqual(204, code)
 | |
| 
 | |
|         user = user.key.get()
 | |
|         self.assertTrue(user.is_enabled(Fake))
 | |
|         self.assertEqual(['efake:user'], ExplicitFake.fetched)
 | |
| 
 | |
|     def test_follow_bot_user_copy_id_refreshes_profile(self):
 | |
|         # bot user
 | |
|         self.make_user('fa.brid.gy', cls=Web,
 | |
|                        copies=[Target(uri='efake:bot', protocol='efake')])
 | |
| 
 | |
|         # profile that's opted out
 | |
|         user = self.make_user('efake:user', cls=ExplicitFake, obj_as1={
 | |
|             'id': 'efake:user',
 | |
|             'summary': '#nobridge',
 | |
|         })
 | |
|         self.assertFalse(user.is_enabled(Fake))
 | |
| 
 | |
|         # updated profile isn't opted out
 | |
|         ExplicitFake.fetchable = {'efake:user': {
 | |
|             'id': 'efake:user',
 | |
|             'summary': 'never mind',
 | |
|         }}
 | |
| 
 | |
|         # follow should refresh profile
 | |
|         _, code = ExplicitFake.receive_as1({
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'id': 'efake:follow',
 | |
|             'actor': 'efake:user',
 | |
|             'object': 'efake:bot',
 | |
|         })
 | |
|         self.assertEqual(204, code)
 | |
| 
 | |
|         user = user.key.get()
 | |
|         self.assertTrue(user.is_enabled(Fake))
 | |
|         self.assertEqual(['efake:user'], ExplicitFake.fetched)
 | |
| 
 | |
|     def test_follow_bot_user_overrides_nobot(self):
 | |
|         # bot user
 | |
|         self.make_user('fa.brid.gy', cls=Web,
 | |
|                        copies=[Target(uri='efake:bot', protocol='efake')])
 | |
| 
 | |
|         # profile that's opted out
 | |
|         actor = {
 | |
|             'id': 'efake:user',
 | |
|             'summary': '#nobot',
 | |
|         }
 | |
|         user = self.make_user('efake:user', cls=ExplicitFake, obj_as1=actor)
 | |
|         self.assertFalse(user.is_enabled(Fake))
 | |
|         ExplicitFake.fetchable = {'efake:user': actor}
 | |
| 
 | |
|         # follow should override #nobot
 | |
|         _, code = ExplicitFake.receive_as1({
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'id': 'efake:follow',
 | |
|             'actor': 'efake:user',
 | |
|             'object': 'efake:bot',
 | |
|         })
 | |
|         self.assertEqual(204, code)
 | |
| 
 | |
|         user = user.key.get()
 | |
|         self.assertIsNone(user.status)
 | |
|         self.assertTrue(user.is_enabled(Fake))
 | |
|         self.assertEqual(['efake:user'], ExplicitFake.fetched)
 | |
| 
 | |
|     @patch.object(ExplicitFake, 'REQUIRES_NAME', new=True)
 | |
|     def test_follow_bot_user_spam_filter_doesnt_enable(self):
 | |
|         self.make_user('fa.brid.gy', cls=Web,
 | |
|                        copies=[Target(uri='efake:bot', protocol='efake')])
 | |
| 
 | |
|         user = self.make_user('efake:user', cls=ExplicitFake)
 | |
|         ExplicitFake.fetchable = {'efake:user': {'id': 'efake:user'}}
 | |
| 
 | |
|         with self.assertRaises(NoContent):
 | |
|             _, code = ExplicitFake.receive_as1({
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'follow',
 | |
|                 'id': 'efake:follow',
 | |
|                 'actor': 'efake:user',
 | |
|                 'object': 'efake:bot',
 | |
|             })
 | |
| 
 | |
|         user = user.key.get()
 | |
|         self.assertEqual('blocked', user.status)
 | |
|         self.assertFalse(user.is_enabled(Fake))
 | |
|         self.assertEqual(['efake:user'], ExplicitFake.fetched)
 | |
| 
 | |
|     def test_block_then_follow_protocol_user_recreates_copy(self):
 | |
|         # bot user
 | |
|         self.make_user('fa.brid.gy', cls=Web)
 | |
| 
 | |
|         follow = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'follow',
 | |
|             'id': 'efake:follow',
 | |
|             'actor': 'efake:user',
 | |
|             'object': 'fa.brid.gy',
 | |
|         }
 | |
|         block = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'block',
 | |
|             'id': 'efake:block',
 | |
|             'actor': 'efake:user',
 | |
|             'object': 'fa.brid.gy',
 | |
|         }
 | |
| 
 | |
|         copy = Target(uri='fake:user', protocol='fake')
 | |
|         user = self.make_user('efake:user', cls=ExplicitFake,
 | |
|                               enabled_protocols=['fake'], copies=[copy])
 | |
|         self.assertTrue(user.is_enabled(Fake))
 | |
|         self.assertEqual([copy], user.copies)
 | |
| 
 | |
|         self.assertEqual(('OK', 200), ExplicitFake.receive_as1(block))
 | |
|         user = user.key.get()
 | |
|         self.assertFalse(user.is_enabled(Fake))
 | |
|         self.assertEqual([copy], user.copies)
 | |
| 
 | |
|         # fake protocol isn't enabled yet, block should be a noop
 | |
|         ExplicitFake.fetchable = {'efake:user': {'profile': 'info'}}
 | |
|         _, code = ExplicitFake.receive_as1(follow)
 | |
|         self.assertEqual(204, code)
 | |
|         user = user.key.get()
 | |
|         self.assertEqual(['fake'], user.enabled_protocols)
 | |
|         self.assertEqual(['efake:user'], Fake.created_for)
 | |
| 
 | |
|     def test_too_old(self):
 | |
|         Follower.get_or_create(to=self.user, from_=self.alice)
 | |
| 
 | |
|         with self.assertRaises(NoContent):
 | |
|             Fake.receive_as1({
 | |
|                 'id': 'fake:post',
 | |
|                 'objectType': 'note',
 | |
|                 'author': 'fake:user',
 | |
|                 'published': '2021-12-14T03:04:05+00:00',  # NOW - 2w
 | |
|             })
 | |
|         self.assertIsNone(Object.get_by_id('fake:post'))
 | |
| 
 | |
|         self.assertEqual([], Fake.sent)
 | |
|         self.assertEqual([], OtherFake.sent)
 | |
| 
 | |
|     def test_too_old_published_without_timezone(self):
 | |
|         Follower.get_or_create(to=self.user, from_=self.alice)
 | |
| 
 | |
|         with self.assertRaises(NoContent):
 | |
|             Fake.receive_as1({
 | |
|                 'id': 'fake:post',
 | |
|                 'objectType': 'note',
 | |
|                 'author': 'fake:user',
 | |
|                 'published': '2021-12-14T03:04:05',  # NOW - 2w
 | |
|             })
 | |
|         self.assertIsNone(Object.get_by_id('fake:post'))
 | |
| 
 | |
|         self.assertEqual([], Fake.sent)
 | |
|         self.assertEqual([], OtherFake.sent)
 | |
| 
 | |
|     def test_receive_activity_lease(self):
 | |
|         Follower.get_or_create(to=self.user, from_=self.alice)
 | |
| 
 | |
|         post_as1 = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'id': 'fake:post',
 | |
|             'actor': 'fake:user',
 | |
|             'object': {
 | |
|                 'id': 'fake:note',
 | |
|                 'objectType': 'note',
 | |
|                 'author': 'fake:user',
 | |
|             },
 | |
|         }
 | |
| 
 | |
|         orig_send = OtherFake.send
 | |
|         at_send = Condition()
 | |
|         continue_send = Condition()
 | |
|         def send(*args, **kwargs):
 | |
|             with at_send:
 | |
|                 at_send.notify()
 | |
|             with continue_send:
 | |
|                 continue_send.wait(10)  # timeout in seconds
 | |
|             return orig_send(*args, **kwargs)
 | |
| 
 | |
|         def receive():
 | |
|             with app.test_request_context('/'), \
 | |
|                  ndb_client.context(
 | |
|                      cache_policy=common.cache_policy,
 | |
|                      global_cache=_InProcessGlobalCache(),
 | |
|                      global_cache_timeout_policy=common.global_cache_timeout_policy):
 | |
|                 try:
 | |
|                     Fake.receive_as1(post_as1)
 | |
|                 except NoContent:  # raised by the second thread
 | |
|                     pass
 | |
| 
 | |
|         first = Thread(target=receive)
 | |
|         second = Thread(target=receive)
 | |
| 
 | |
|         with patch.object(OtherFake, 'send', side_effect=send):
 | |
|             first.start()
 | |
|             second.start()
 | |
|             with at_send:
 | |
|                 at_send.wait(10)  # timeout in seconds
 | |
|             with continue_send:
 | |
|                 continue_send.notify(1)
 | |
|             first.join()
 | |
|             second.join()
 | |
| 
 | |
|         # only one receive call should try to send
 | |
|         self.assertEqual([('other:alice:target', post_as1)], OtherFake.sent)
 | |
| 
 | |
|     @patch('protocol.LIMITED_DOMAINS', ['lim.it'])
 | |
|     @patch('requests.get')
 | |
|     def test_limited_domain_update_profile_without_follow(self, mock_get):
 | |
|         actor = {
 | |
|             **ACTOR,
 | |
|             'id': 'https://lim.it/alice',
 | |
|         }
 | |
|         mock_get.side_effect = [
 | |
|             self.as2_resp(actor),
 | |
|         ]
 | |
| 
 | |
|         _, code = got = ActivityPub.receive(Object(our_as1={
 | |
|             'id': 'https://lim.it/alice#update',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'update',
 | |
|             'actor': 'https://lim.it/alice',
 | |
|             'object': actor,
 | |
|         }), authed_as='https://lim.it/alice')
 | |
|         self.assertEqual(204, code)
 | |
| 
 | |
|         self.assert_object('https://lim.it/alice',
 | |
|                            source_protocol='activitypub',
 | |
|                            our_as1=actor,
 | |
|                            users=[ActivityPub(id='https://lim.it/alice').key],
 | |
|                            )
 | |
| 
 | |
|     @patch('protocol.LIMITED_DOMAINS', ['lim.it'])
 | |
|     @patch.object(ATProto, 'send')
 | |
|     @patch('requests.get')
 | |
|     def test_inbox_limited_domain_create_without_follow_no_atproto(
 | |
|             self, mock_get, mock_send):
 | |
|         actor = 'https://lim.it/alice'
 | |
|         user = self.make_user(id=actor, cls=ActivityPub, enabled_protocols=['atproto'])
 | |
| 
 | |
|         # follow by bot user shouldn't count
 | |
|         Follower.get_or_create(to=user, from_=Web(id='https://bsky.brid.gy/'))
 | |
| 
 | |
|         _, code = ActivityPub.receive(Object(as2={
 | |
|             **NOTE,
 | |
|             'id': 'https://lim.it/note',
 | |
|             'actor': actor,
 | |
|         }), authed_as=actor)
 | |
|         self.assertEqual(204, code)
 | |
| 
 | |
|         mock_send.assert_not_called()
 | |
| 
 | |
|     def test_receive_task_handler_obj_id(self):
 | |
|         note = {
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:other',
 | |
|         }
 | |
|         self.store_object(id='fake:post', our_as1=note, source_protocol='fake')
 | |
| 
 | |
|         create = {
 | |
|             'id': 'fake:post#bridgy-fed-create',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'object': note,
 | |
|             'actor': 'fake:other',
 | |
|         }
 | |
|         self.store_object(id='fake:post#bridgy-fed-create',
 | |
|                           source_protocol='fake', our_as1=create)
 | |
| 
 | |
|         resp = self.post('/queue/receive', data={
 | |
|             'obj_id': 'fake:post#bridgy-fed-create',
 | |
|             'authed_as': 'fake:other',
 | |
|         }, headers={'X-AppEngine-TaskRetryCount': '0'})
 | |
|         self.assertEqual(204, resp.status_code)
 | |
|         obj = Object.get_by_id('fake:post#bridgy-fed-create')
 | |
| 
 | |
|     def test_receive_task_handler_properties(self):
 | |
|         note = {
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:other',
 | |
|         }
 | |
|         create = {
 | |
|             'id': 'fake:post#bridgy-fed-create',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'object': note,
 | |
|             'actor': 'fake:other',
 | |
|         }
 | |
| 
 | |
|         resp = self.post('/queue/receive', data={
 | |
|             'our_as1': json_dumps(note),
 | |
|             'source_protocol': 'fake',
 | |
|             'authed_as': 'fake:other',
 | |
|         }, headers={'X-AppEngine-TaskRetryCount': '0'})
 | |
|         self.assertEqual(204, resp.status_code)
 | |
| 
 | |
|         obj = Object.get_by_id('fake:post')
 | |
|         self.assertEqual(note, obj.our_as1)
 | |
|         self.assertIsNone(Object.get_by_id('fake:post#bridgy-fed-create'))
 | |
| 
 | |
|     @patch.object(Fake, 'receive', side_effect=requests.ConnectionError('foo'))
 | |
|     def test_receive_task_handler_connection_error(self, _):
 | |
|         orig_count = Object.query().count()
 | |
|         got = self.post('/queue/receive', data={
 | |
|             'our_as1': json_dumps({'id': 'fake:post'}),
 | |
|             'source_protocol': 'fake',
 | |
|         })
 | |
|         self.assertEqual(304, got.status_code)
 | |
|         self.assertEqual(orig_count, Object.query().count())
 | |
| 
 | |
|     def test_receive_task_handler_authed_as(self):
 | |
|         note = {
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
| 
 | |
|         got = self.post('/queue/receive', data={
 | |
|             'our_as1': json_dumps(note),
 | |
|             'source_protocol': 'fake',
 | |
|             'authed_as': 'fake:user',
 | |
|         })
 | |
|         self.assertEqual(204, got.status_code)
 | |
|         self.assertEqual(note, Object.get_by_id('fake:post').our_as1)
 | |
| 
 | |
|     def test_receive_task_handler_authed_as_domain_vs_homepage(self):
 | |
|         user = self.make_user('user.com', cls=Web, obj_id='https://user.com/')
 | |
|         note = {
 | |
|             'id': 'https://user.com/c',
 | |
|             'objectType': 'note',
 | |
|             'author': 'https://user.com/',
 | |
|         }
 | |
| 
 | |
|         got = self.post('/queue/receive', data={
 | |
|             'our_as1': json_dumps(note),
 | |
|             'source_protocol': 'web',
 | |
|             'authed_as': 'user.com',
 | |
|         })
 | |
|         self.assertEqual(204, got.status_code)
 | |
|         self.assertEqual({
 | |
|             **note,
 | |
|             'author': 'user.com',
 | |
|         }, Object.get_by_id('https://user.com/c').our_as1)
 | |
| 
 | |
|     @patch('requests.get', side_effect=web_user_gets('foo.com') + [ACTOR_HTML_RESP])
 | |
|     def test_receive_task_handler_authed_as_www_subdomain(self, _):
 | |
|         note = {
 | |
|             'id': 'http://www.foo.com/post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'http://www.foo.com/bar',
 | |
|         }
 | |
| 
 | |
|         got = self.post('/queue/receive', data={
 | |
|             'our_as1': json_dumps(note),
 | |
|             'source_protocol': 'web',
 | |
|             'authed_as': 'foo.com',
 | |
|         })
 | |
|         self.assertEqual(204, got.status_code)
 | |
|         self.assertEqual(note, Object.get_by_id('http://www.foo.com/post').our_as1)
 | |
| 
 | |
|     @patch('requests.get', return_value=requests_response('<html></html>'))
 | |
|     def test_receive_task_handler_authed_as_mixed_subdomains(self, _):
 | |
|         user = self.make_user('user.com', cls=Web, obj_id='https://user.com/')
 | |
|         note = {
 | |
|             'objectType': 'note',
 | |
|             'id': 'http://user.com/post',
 | |
|             'author': 'http://m.user.com/',
 | |
|         }
 | |
| 
 | |
|         got = self.post('/queue/receive', data={
 | |
|             'our_as1': json_dumps(note),
 | |
|             'source_protocol': 'web',
 | |
|             'authed_as': 'www.user.com',
 | |
|         })
 | |
|         self.assertEqual(204, got.status_code)
 | |
|         self.assertEqual(note, Object.get_by_id('http://user.com/post').our_as1)
 | |
| 
 | |
|     @patch('requests.get', return_value=requests_response('<html></html>'))
 | |
|     def test_receive_task_handler_authed_as_wrong_domain(self, _):
 | |
|         note = {
 | |
|             'id': 'http://bar.com/post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'http://bar.com/',
 | |
|         }
 | |
| 
 | |
|         got = self.post('/queue/receive', data={
 | |
|             'our_as1': json_dumps(note),
 | |
|             'source_protocol': 'web',
 | |
|             'authed_as': 'foo.com',
 | |
|         })
 | |
|         self.assertEqual(299, got.status_code)
 | |
|         self.assertIsNone(Object.get_by_id('https://bar.com/post'))
 | |
| 
 | |
|     def test_receive_task_handler_not_authed_as(self):
 | |
|         note = {
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:other',
 | |
|         }
 | |
| 
 | |
|         got = self.post('/queue/receive', data={
 | |
|             'our_as1': json_dumps(note),
 | |
|             'source_protocol': 'fake',
 | |
|             'authed_as': 'fake:eve',
 | |
|         })
 | |
|         self.assertEqual(299, got.status_code)
 | |
|         self.assertIsNone(Object.get_by_id('fake:post'))
 | |
| 
 | |
|     def test_like_not_authed_as_actor(self):
 | |
|         Fake.fetchable['fake:post'] = {
 | |
|             'objectType': 'note',
 | |
|             'author': 'other:bob',
 | |
|         }
 | |
| 
 | |
|         with self.assertRaises(ErrorButDoNotRetryTask):
 | |
|             Fake.receive_as1({
 | |
|                 'id': 'fake:like',
 | |
|                 'objectType': 'activity',
 | |
|                 'verb': 'like',
 | |
|                 'actor': 'fake:user',
 | |
|                 'object': 'fake:post',
 | |
|             }, authed_as='fake:other')
 | |
| 
 | |
|         self.assertIsNone(Object.get_by_id('fake:like'))
 | |
| 
 | |
|     def test_user_opted_out(self):
 | |
|         self.make_followers()
 | |
|         self.user.obj.our_as1 = {'summary': '#nobot'}
 | |
|         self.user.obj.put()
 | |
| 
 | |
|         with self.assertRaises(NoContent):
 | |
|             Fake.receive_as1({
 | |
|                 'id': 'fake:post',
 | |
|                 'objectType': 'note',
 | |
|                 'author': 'fake:user',
 | |
|             })
 | |
| 
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     @patch('oauth_dropins.webutil.appengine_config.tasks_client.create_task')
 | |
|     def test_from_protocol_unsupported_types(self, mock_create_task):
 | |
|         common.RUN_TASKS_INLINE = False
 | |
|         self.make_followers()
 | |
| 
 | |
|         event = {
 | |
|             'id': 'fake:event',
 | |
|             'objectType': 'event',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         post_event = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'id': 'fake:post-event',
 | |
|             'object': event,
 | |
|         }
 | |
|         add = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'add',
 | |
|             'id': 'fake:add',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'fake:thing',
 | |
|         }
 | |
| 
 | |
|         for activity in event, post_event, add:
 | |
|             with self.subTest(activity=activity):
 | |
|                 with self.assertRaises(NoContent):
 | |
|                     Fake.receive_as1(activity)
 | |
|                     self.assertEqual([], Fake.sent)
 | |
|                     mock_create_task.assert_not_called()
 | |
| 
 | |
|     @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()
 | |
| 
 | |
|         note_as1 = {
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(note_as1))
 | |
| 
 | |
|         create_as1 = {
 | |
|             'id': 'fake:post#bridgy-fed-create',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'actor': 'fake:user',
 | |
|             'object': note_as1,
 | |
|             'published': '2022-01-02T03:04:05+00:00',
 | |
|         }
 | |
|         self.assertEqual(2, mock_create_task.call_count)
 | |
|         self.assert_task(mock_create_task, 'send', source_protocol='fake',
 | |
|                          protocol='other', id='fake:post#bridgy-fed-create',
 | |
|                          our_as1=create_as1, url='other:alice:target',
 | |
|                          user=self.user.key.urlsafe())
 | |
|         self.assert_task(mock_create_task, 'send', source_protocol='fake',
 | |
|                          protocol='other', id='fake:post#bridgy-fed-create',
 | |
|                          our_as1=create_as1, url='other:bob: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
 | |
|         eve = self.make_user('other:eve', cls=OtherFake, obj_id='other:eve')
 | |
| 
 | |
|         reply_as1 = {
 | |
|             'id': 'fake:reply',
 | |
|             'objectType': 'note',
 | |
|             'inReplyTo': 'other:post',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         OtherFake.fetchable['other:post'] = {
 | |
|             'objectType': 'note',
 | |
|             'id': 'other:post',
 | |
|             'author': 'other:eve',
 | |
|         }
 | |
|         self.assertEqual(('OK', 202), Fake.receive_as1(reply_as1))
 | |
| 
 | |
|         self.assert_object('fake:reply',
 | |
|                            our_as1=reply_as1,
 | |
|                            type='note',
 | |
|                            users=[self.user.key],
 | |
|                            notify=[eve.key],
 | |
|                            )
 | |
| 
 | |
|         create_as1 = {
 | |
|             'id': 'fake:reply#bridgy-fed-create',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'actor': 'fake:user',
 | |
|             'object': reply_as1,
 | |
|             'published': '2022-01-02T03:04:05+00:00',
 | |
|         }
 | |
|         self.assert_task(mock_create_task, 'send', source_protocol='fake',
 | |
|                          protocol='other', orig_obj_id='other:post',
 | |
|                          id='fake:reply#bridgy-fed-create', our_as1=create_as1,
 | |
|                          url='other:post:target', user=self.user.key.urlsafe())
 | |
| 
 | |
|         self.assertEqual([], OtherFake.sent)
 | |
|         self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     def test_send_task_handler_obj_id(self):
 | |
|         self.make_followers()
 | |
| 
 | |
|         note = self.store_object(id='fake:note', our_as1={
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|         })
 | |
|         target = Target(uri='fake:shared:target', protocol='fake')
 | |
|         create = self.store_object(id='fake:create', our_as1={
 | |
|             'id': 'fake:create',
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'actor': 'fake:user',
 | |
|             'object': note.as1,
 | |
|         })
 | |
|         resp = self.post('/queue/send', data={
 | |
|             'protocol': 'fake',
 | |
|             'obj_id': 'fake:create',
 | |
|             'orig_obj_id': 'fake:note',
 | |
|             'url': 'fake:shared:target',
 | |
|             'user': self.user.key.urlsafe(),
 | |
|         }, headers={'X-AppEngine-TaskRetryCount': '0'})
 | |
|         self.assertEqual(200, resp.status_code)
 | |
| 
 | |
|     def test_send_task_missing_url(self):
 | |
|         obj = self.store_object(id='fake:post')
 | |
|         resp = self.post('/queue/send', data={
 | |
|             'protocol': 'fake',
 | |
|             'obj': obj.key.urlsafe(),
 | |
|             'url': None,
 | |
|             'user': self.user.key.urlsafe(),
 | |
|         })
 | |
|         self.assertEqual(204, resp.status_code)
 | |
| 
 | |
|     @patch.object(Fake, 'send', return_value=False)
 | |
|     def test_send_returns_false_task_returns_204(self, mock_send):
 | |
|         target = Target(protocol='fake', uri='fake:target')
 | |
|         obj = self.store_object(id='fake:post', our_as1={
 | |
|             'objectType': 'note',
 | |
|         })
 | |
|         resp = self.post('/queue/send', data={
 | |
|             'protocol': 'fake',
 | |
|             'obj_id': 'fake:post',
 | |
|             'url': 'fake:target',
 | |
|         })
 | |
|         self.assertEqual(204, resp.status_code)
 | |
| 
 | |
|     def test_send_unsupported_types(self):
 | |
|         target = Target(protocol='fake', uri='fake:target')
 | |
| 
 | |
|         event = {
 | |
|             'id': 'fake:event',
 | |
|             'objectType': 'event',
 | |
|             'author': 'fake:user',
 | |
|         }
 | |
|         post_event = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'post',
 | |
|             'id': 'fake:post-event',
 | |
|             'object': event,
 | |
|         }
 | |
|         add = {
 | |
|             'objectType': 'activity',
 | |
|             'verb': 'add',
 | |
|             'id': 'fake:add',
 | |
|             'actor': 'fake:user',
 | |
|             'object': 'fake:thing',
 | |
|         }
 | |
| 
 | |
|         for activity in event, post_event, add:
 | |
|             with self.subTest(activity=activity):
 | |
|                 self.store_object(id=activity['id'], our_as1=activity)
 | |
|                 resp = self.post('/queue/send', data={
 | |
|                     'protocol': 'fake',
 | |
|                     'obj_id': activity['id'],
 | |
|                     'url': 'fake:target',
 | |
|                 })
 | |
|                 self.assertEqual(204, resp.status_code)
 | |
|                 self.assertEqual([], Fake.sent)
 | |
| 
 | |
|     @patch.object(Fake, 'send', return_value=True)
 | |
|     def test_send_task_follow_user_use_instead(self, mock_send):
 | |
|         self.bob.use_instead = self.alice.key
 | |
|         self.bob.put()
 | |
| 
 | |
|         target = Target(uri='fake:target', protocol='fake')
 | |
|         self.store_object(id='fake:note', our_as1={
 | |
|             'id': 'fake:post',
 | |
|             'objectType': 'note',
 | |
|         })
 | |
|         resp = self.post('/queue/send', data={
 | |
|             'protocol': 'fake',
 | |
|             'obj_id': 'fake:note',
 | |
|             'url': 'fake:target',
 | |
|             'user': self.bob.key.urlsafe(),
 | |
|         })
 | |
| 
 | |
|         _, kwargs = mock_send.call_args
 | |
|         self.assertEqual('other:alice', kwargs['from_user'].key.id())
 |