2023-03-14 00:25:10 +00:00
|
|
|
"""Unit tests for protocol.py."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
|
2023-03-20 21:28:14 +00:00
|
|
|
from flask import g
|
2023-03-14 00:25:10 +00:00
|
|
|
from oauth_dropins.webutil.testutil import requests_response
|
|
|
|
import requests
|
|
|
|
|
2023-05-30 23:36:18 +00:00
|
|
|
# import first so that Fake is defined before URL routes are registered
|
|
|
|
from .testutil import Fake, TestCase
|
|
|
|
|
2023-06-11 02:50:31 +00:00
|
|
|
from activitypub import ActivityPub
|
|
|
|
from app import app
|
2023-05-26 23:07:36 +00:00
|
|
|
from models import Follower, Object, PROTOCOLS, User
|
2023-06-18 14:29:54 +00:00
|
|
|
import protocol
|
2023-06-11 02:50:31 +00:00
|
|
|
from protocol import Protocol
|
2023-06-18 14:29:54 +00:00
|
|
|
import requests
|
2023-06-13 20:17:11 +00:00
|
|
|
from ui import UIProtocol
|
2023-05-27 00:40:29 +00:00
|
|
|
from web import Web
|
2023-03-14 00:25:10 +00:00
|
|
|
|
|
|
|
from .test_activitypub import ACTOR, REPLY
|
2023-06-13 20:17:11 +00:00
|
|
|
from .test_web import ACTOR_HTML
|
2023-03-14 00:25:10 +00:00
|
|
|
|
2023-03-14 03:38:17 +00:00
|
|
|
REPLY = {
|
|
|
|
**REPLY,
|
|
|
|
'actor': ACTOR,
|
|
|
|
'object': {
|
|
|
|
**REPLY['object'],
|
|
|
|
'author': ACTOR,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-03-14 00:25:10 +00:00
|
|
|
|
2023-05-26 23:07:36 +00:00
|
|
|
class ProtocolTest(TestCase):
|
2023-03-14 00:25:10 +00:00
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
super().setUp()
|
|
|
|
self.user = self.make_user('foo.com', has_hcard=True)
|
2023-03-20 21:28:14 +00:00
|
|
|
g.user = None
|
2023-03-14 00:25:10 +00:00
|
|
|
|
|
|
|
def tearDown(self):
|
2023-06-13 20:17:11 +00:00
|
|
|
PROTOCOLS.pop('greedy', None)
|
2023-03-14 00:25:10 +00:00
|
|
|
super().tearDown()
|
|
|
|
|
2023-05-23 22:15:28 +00:00
|
|
|
def test_protocols_global(self):
|
2023-05-26 23:07:36 +00:00
|
|
|
self.assertEqual(Fake, PROTOCOLS['fake'])
|
2023-05-30 19:15:36 +00:00
|
|
|
self.assertEqual(Web, PROTOCOLS['web'])
|
2023-05-27 00:40:29 +00:00
|
|
|
self.assertEqual(Web, PROTOCOLS['webmention'])
|
2023-05-23 22:15:28 +00:00
|
|
|
|
2023-06-12 21:17:44 +00:00
|
|
|
def test_for_domain_for_request(self):
|
2023-06-13 05:01:12 +00:00
|
|
|
for domain, expected in [
|
2023-06-12 21:17:44 +00:00
|
|
|
('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),
|
2023-06-12 22:37:17 +00:00
|
|
|
('fed.brid.gy', None),
|
2023-06-12 21:17:44 +00:00
|
|
|
('fake.fed.brid.gy', None),
|
|
|
|
('fake', None),
|
|
|
|
('fake.com', None),
|
|
|
|
]:
|
2023-06-13 05:01:12 +00:00
|
|
|
with self.subTest(domain=domain, expected=expected):
|
|
|
|
self.assertEqual(expected, Protocol.for_domain(domain))
|
2023-06-12 22:37:17 +00:00
|
|
|
with app.test_request_context('/foo', base_url=f'https://{domain}/'):
|
2023-06-13 05:01:12 +00:00
|
|
|
self.assertEqual(expected, Protocol.for_request())
|
|
|
|
|
|
|
|
def test_for_domain_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_domain(url, fed=Fake))
|
|
|
|
with app.test_request_context('/foo', base_url=url):
|
|
|
|
self.assertEqual(expected, Protocol.for_request(fed=Fake))
|
2023-06-13 03:51:32 +00:00
|
|
|
|
2023-06-14 21:57:59 +00:00
|
|
|
def test_subdomain_url(self):
|
|
|
|
self.assertEqual('https://fa.brid.gy/', Fake.subdomain_url())
|
|
|
|
self.assertEqual('https://fa.brid.gy/foo?bar', Fake.subdomain_url('foo?bar'))
|
|
|
|
self.assertEqual('https://fed.brid.gy/', UIProtocol.subdomain_url())
|
|
|
|
|
2023-03-14 00:25:10 +00:00
|
|
|
@patch('requests.get')
|
2023-03-14 03:38:17 +00:00
|
|
|
def test_receive_reply_not_feed_not_notification(self, mock_get):
|
2023-06-06 21:50:20 +00:00
|
|
|
Follower.get_or_create(to=Fake.get_or_create(id=ACTOR['id']),
|
|
|
|
from_=Fake.get_or_create(id='foo.com'))
|
2023-06-09 19:56:45 +00:00
|
|
|
other_user = self.make_user('user.com', cls=Web)
|
2023-03-14 00:25:10 +00:00
|
|
|
|
2023-03-19 22:43:55 +00:00
|
|
|
# user.com webmention discovery
|
2023-03-14 00:25:10 +00:00
|
|
|
mock_get.return_value = requests_response('<html></html>')
|
|
|
|
|
2023-06-11 15:14:17 +00:00
|
|
|
Fake.receive(REPLY['id'], as2=REPLY)
|
2023-03-14 03:38:17 +00:00
|
|
|
|
|
|
|
self.assert_object(REPLY['id'],
|
|
|
|
as2=REPLY,
|
2023-03-14 00:25:10 +00:00
|
|
|
type='post',
|
2023-06-09 19:56:45 +00:00
|
|
|
users=[other_user.key],
|
2023-03-14 03:38:17 +00:00
|
|
|
# not feed since it's a reply
|
|
|
|
# not notification since it doesn't involve the user
|
|
|
|
labels=['activity'],
|
2023-03-14 00:25:10 +00:00
|
|
|
status='complete',
|
2023-06-11 15:14:17 +00:00
|
|
|
source_protocol='fake',
|
2023-03-14 00:25:10 +00:00
|
|
|
)
|
2023-03-14 03:38:17 +00:00
|
|
|
self.assert_object(REPLY['object']['id'],
|
|
|
|
as2=REPLY['object'],
|
2023-03-14 00:25:10 +00:00
|
|
|
type='comment',
|
2023-06-11 15:14:17 +00:00
|
|
|
source_protocol='fake',
|
2023-03-14 00:25:10 +00:00
|
|
|
)
|
2023-03-27 21:12:06 +00:00
|
|
|
|
2023-06-13 20:17:11 +00:00
|
|
|
def test_for_id(self):
|
2023-06-13 20:43:41 +00:00
|
|
|
for id, expected in [
|
|
|
|
(None, None),
|
|
|
|
('', None),
|
|
|
|
('foo://bar', None),
|
2023-06-15 17:52:11 +00:00
|
|
|
('fake:foo', Fake),
|
2023-06-13 20:43:41 +00:00
|
|
|
# TODO
|
|
|
|
# ('at://foo', ATProto),
|
|
|
|
('https://ap.brid.gy/foo/bar', ActivityPub),
|
|
|
|
('https://web.brid.gy/foo/bar', Web),
|
|
|
|
]:
|
|
|
|
self.assertEqual(expected, Protocol.for_id(id))
|
2023-06-13 20:17:11 +00:00
|
|
|
|
|
|
|
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):
|
|
|
|
Object(id='http://ui/obj', source_protocol='ui').put()
|
|
|
|
self.assertEqual(UIProtocol, Protocol.for_id('http://ui/obj'))
|
|
|
|
|
2023-06-14 04:36:56 +00:00
|
|
|
def test_for_id_object_missing_source_protocol(self):
|
|
|
|
Object(id='http://bad/obj').put()
|
|
|
|
self.assertIsNone(Protocol.for_id('http://bad/obj'))
|
|
|
|
|
2023-06-13 20:17:11 +00:00
|
|
|
@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/actor'))
|
|
|
|
self.assertIn(self.as2_req('http://ap/actor'), mock_get.mock_calls)
|
|
|
|
|
|
|
|
@patch('requests.get')
|
|
|
|
def test_for_id_web_fetch(self, mock_get):
|
|
|
|
mock_get.return_value = requests_response(ACTOR_HTML)
|
|
|
|
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_no_mf2(self, mock_get):
|
|
|
|
mock_get.return_value = requests_response('<html></html>')
|
|
|
|
self.assertIsNone(Protocol.for_id('http://web.site/'))
|
|
|
|
self.assertIn(self.req('http://web.site/'), mock_get.mock_calls)
|
|
|
|
|
2023-03-29 20:13:32 +00:00
|
|
|
def test_load(self):
|
2023-05-26 23:07:36 +00:00
|
|
|
Fake.objects['foo'] = {'x': 'y'}
|
2023-04-03 14:53:15 +00:00
|
|
|
|
2023-05-26 23:07:36 +00:00
|
|
|
loaded = Fake.load('foo')
|
2023-04-03 14:53:15 +00:00
|
|
|
self.assert_equals({'x': 'y'}, loaded.our_as1)
|
|
|
|
self.assertFalse(loaded.changed)
|
|
|
|
self.assertTrue(loaded.new)
|
|
|
|
|
2023-03-27 21:12:06 +00:00
|
|
|
self.assertIsNotNone(Object.get_by_id('foo'))
|
2023-05-26 23:07:36 +00:00
|
|
|
self.assertEqual(['foo'], Fake.fetched)
|
2023-03-27 21:12:06 +00:00
|
|
|
|
2023-06-03 04:53:44 +00:00
|
|
|
def test_load_existing(self):
|
2023-03-27 21:12:06 +00:00
|
|
|
stored = Object(id='foo', our_as1={'x': 'y'})
|
|
|
|
stored.put()
|
2023-04-03 14:53:15 +00:00
|
|
|
|
2023-05-26 23:07:36 +00:00
|
|
|
loaded = Fake.load('foo')
|
2023-04-03 14:53:15 +00:00
|
|
|
self.assert_equals({'x': 'y'}, loaded.our_as1)
|
|
|
|
self.assertFalse(loaded.changed)
|
|
|
|
self.assertFalse(loaded.new)
|
|
|
|
|
2023-05-26 23:07:36 +00:00
|
|
|
self.assertEqual([], Fake.fetched)
|
2023-03-29 19:48:50 +00:00
|
|
|
|
2023-06-03 04:53:44 +00:00
|
|
|
def test_load_existing_empty_deleted(self):
|
2023-03-29 19:48:50 +00:00
|
|
|
stored = Object(id='foo', deleted=True)
|
|
|
|
stored.put()
|
|
|
|
|
2023-05-26 23:07:36 +00:00
|
|
|
loaded = Fake.load('foo')
|
2023-04-03 14:53:15 +00:00
|
|
|
self.assert_entities_equal(stored, loaded)
|
|
|
|
self.assertFalse(loaded.changed)
|
|
|
|
self.assertFalse(loaded.new)
|
|
|
|
|
2023-05-26 23:07:36 +00:00
|
|
|
self.assertEqual([], Fake.fetched)
|
2023-04-03 14:53:15 +00:00
|
|
|
|
2023-06-18 14:29:54 +00:00
|
|
|
def test_load_remote_true_existing_empty(self):
|
2023-06-03 04:53:44 +00:00
|
|
|
Fake.objects['foo'] = {'x': 'y'}
|
|
|
|
Object(id='foo').put()
|
|
|
|
|
2023-06-18 14:29:54 +00:00
|
|
|
loaded = Fake.load('foo', remote=True)
|
2023-06-03 04:53:44 +00:00
|
|
|
self.assertEqual({'x': 'y'}, loaded.as1)
|
|
|
|
self.assertTrue(loaded.changed)
|
|
|
|
self.assertFalse(loaded.new)
|
|
|
|
self.assertEqual(['foo'], Fake.fetched)
|
|
|
|
|
2023-06-18 14:29:54 +00:00
|
|
|
def test_load_remote_true_new_empty(self):
|
2023-06-03 04:53:44 +00:00
|
|
|
Fake.objects['foo'] = None
|
|
|
|
Object(id='foo', our_as1={'x': 'y'}).put()
|
|
|
|
|
2023-06-18 14:29:54 +00:00
|
|
|
loaded = Fake.load('foo', remote=True)
|
2023-06-03 04:53:44 +00:00
|
|
|
self.assertIsNone(loaded.as1)
|
|
|
|
self.assertTrue(loaded.changed)
|
|
|
|
self.assertFalse(loaded.new)
|
|
|
|
self.assertEqual(['foo'], Fake.fetched)
|
|
|
|
|
2023-06-18 14:29:54 +00:00
|
|
|
def test_load_remote_true_unchanged(self):
|
2023-04-03 14:53:15 +00:00
|
|
|
obj = Object(id='foo', our_as1={'x': 'stored'})
|
|
|
|
obj.put()
|
2023-05-26 23:07:36 +00:00
|
|
|
Fake.objects['foo'] = {'x': 'stored'}
|
2023-04-03 14:53:15 +00:00
|
|
|
|
2023-06-18 14:29:54 +00:00
|
|
|
loaded = Fake.load('foo', remote=True)
|
2023-04-03 14:53:15 +00:00
|
|
|
self.assert_entities_equal(obj, loaded)
|
|
|
|
self.assertFalse(obj.changed)
|
|
|
|
self.assertFalse(obj.new)
|
2023-05-26 23:07:36 +00:00
|
|
|
self.assertEqual(['foo'], Fake.fetched)
|
2023-04-03 14:53:15 +00:00
|
|
|
|
2023-06-18 14:29:54 +00:00
|
|
|
def test_load_remote_true_changed(self):
|
2023-04-03 14:53:15 +00:00
|
|
|
Object(id='foo', our_as1={'content': 'stored'}).put()
|
2023-05-26 23:07:36 +00:00
|
|
|
Fake.objects['foo'] = {'content': 'new'}
|
2023-04-03 14:53:15 +00:00
|
|
|
|
2023-06-18 14:29:54 +00:00
|
|
|
loaded = Fake.load('foo', remote=True)
|
2023-04-03 14:53:15 +00:00
|
|
|
self.assert_equals({'content': 'new'}, loaded.our_as1)
|
|
|
|
self.assertTrue(loaded.changed)
|
|
|
|
self.assertFalse(loaded.new)
|
2023-05-26 23:07:36 +00:00
|
|
|
self.assertEqual(['foo'], Fake.fetched)
|
2023-06-17 21:13:17 +00:00
|
|
|
|
2023-06-18 14:29:54 +00:00
|
|
|
def test_load_remote_false(self):
|
|
|
|
self.assertIsNone(Fake.load('nope', remote=False))
|
2023-06-17 21:13:17 +00:00
|
|
|
self.assertEqual([], Fake.fetched)
|
|
|
|
|
|
|
|
obj = Object(id='foo', our_as1={'content': 'stored'})
|
|
|
|
obj.put()
|
2023-06-18 14:29:54 +00:00
|
|
|
self.assert_entities_equal(obj, Fake.load('foo', remote=False))
|
2023-06-17 21:13:17 +00:00
|
|
|
self.assertEqual([], Fake.fetched)
|
2023-06-18 14:29:54 +00:00
|
|
|
|
|
|
|
def test_local_false_missing(self):
|
|
|
|
with self.assertRaises(requests.HTTPError) as e:
|
|
|
|
Fake.load('foo', local=False)
|
|
|
|
self.assertEqual(410, e.response.status_code)
|
|
|
|
|
|
|
|
self.assertEqual(['foo'], Fake.fetched)
|
|
|
|
|
|
|
|
def test_local_false_existing(self):
|
|
|
|
obj = Object(id='foo', our_as1={'content': 'stored'}, source_protocol='ui')
|
|
|
|
obj.put()
|
|
|
|
del protocol.objects_cache['foo']
|
|
|
|
|
|
|
|
Fake.objects['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_remote_false_local_false_assert(self):
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
Fake.load('nope', local=False, remote=False)
|