2017-08-19 15:36:55 +00:00
|
|
|
# coding=utf-8
|
|
|
|
"""Unit tests for models.py."""
|
2022-11-19 02:46:27 +00:00
|
|
|
from unittest import mock
|
|
|
|
|
2023-02-14 16:25:41 +00:00
|
|
|
from flask import get_flashed_messages
|
2023-01-19 06:20:15 +00:00
|
|
|
from granary import as2
|
2022-11-19 02:46:27 +00:00
|
|
|
from oauth_dropins.webutil.testutil import requests_response
|
|
|
|
|
2021-07-11 23:50:44 +00:00
|
|
|
from app import app
|
2023-02-16 16:21:56 +00:00
|
|
|
import common
|
2023-01-28 15:48:50 +00:00
|
|
|
from models import Follower, Object, User
|
2023-03-08 21:10:41 +00:00
|
|
|
from protocol import Protocol
|
2019-12-26 06:20:57 +00:00
|
|
|
from . import testutil
|
2017-08-19 15:36:55 +00:00
|
|
|
|
2023-01-19 06:20:15 +00:00
|
|
|
from .test_activitypub import ACTOR
|
2017-08-19 15:36:55 +00:00
|
|
|
|
2022-11-16 06:00:28 +00:00
|
|
|
class UserTest(testutil.TestCase):
|
2017-08-19 15:36:55 +00:00
|
|
|
|
2017-09-19 14:13:35 +00:00
|
|
|
def setUp(self):
|
2022-11-16 06:00:28 +00:00
|
|
|
super(UserTest, self).setUp()
|
2023-03-10 23:13:45 +00:00
|
|
|
self.user = self.make_user('y.z')
|
2017-09-19 14:13:35 +00:00
|
|
|
|
2023-02-14 16:25:41 +00:00
|
|
|
self.full_redir = requests_response(
|
|
|
|
status=302,
|
|
|
|
redirected_url='http://localhost/.well-known/webfinger?resource=acct:y.z@y.z')
|
|
|
|
|
2022-11-20 17:38:46 +00:00
|
|
|
def test_get_or_create(self):
|
2022-11-16 06:00:28 +00:00
|
|
|
assert self.user.mod
|
|
|
|
assert self.user.public_exponent
|
|
|
|
assert self.user.private_exponent
|
2017-08-19 15:36:55 +00:00
|
|
|
|
2022-11-16 06:00:28 +00:00
|
|
|
same = User.get_or_create('y.z')
|
|
|
|
self.assertEqual(same, self.user)
|
2017-08-19 16:15:29 +00:00
|
|
|
|
2022-12-02 18:48:16 +00:00
|
|
|
def test_get_or_create_use_instead(self):
|
|
|
|
user = User.get_or_create('a.b')
|
|
|
|
user.use_instead = self.user.key
|
|
|
|
user.put()
|
|
|
|
|
|
|
|
self.assertEqual('y.z', User.get_or_create('a.b').key.id())
|
|
|
|
|
2017-08-19 16:15:29 +00:00
|
|
|
def test_href(self):
|
2022-11-16 06:00:28 +00:00
|
|
|
href = self.user.href()
|
2017-08-19 16:15:29 +00:00
|
|
|
self.assertTrue(href.startswith('data:application/magic-public-key,RSA.'), href)
|
2022-11-16 06:00:28 +00:00
|
|
|
self.assertIn(self.user.mod, href)
|
|
|
|
self.assertIn(self.user.public_exponent, href)
|
2017-09-19 14:13:35 +00:00
|
|
|
|
|
|
|
def test_public_pem(self):
|
2022-11-16 06:00:28 +00:00
|
|
|
pem = self.user.public_pem()
|
2019-12-26 06:20:57 +00:00
|
|
|
self.assertTrue(pem.decode().startswith('-----BEGIN PUBLIC KEY-----\n'), pem)
|
|
|
|
self.assertTrue(pem.decode().endswith('-----END PUBLIC KEY-----'), pem)
|
2017-09-19 14:13:35 +00:00
|
|
|
|
2019-12-26 06:20:57 +00:00
|
|
|
def test_private_pem(self):
|
2022-11-16 06:00:28 +00:00
|
|
|
pem = self.user.private_pem()
|
2019-12-26 06:20:57 +00:00
|
|
|
self.assertTrue(pem.decode().startswith('-----BEGIN RSA PRIVATE KEY-----\n'), pem)
|
|
|
|
self.assertTrue(pem.decode().endswith('-----END RSA PRIVATE KEY-----'), pem)
|
2017-10-11 05:42:19 +00:00
|
|
|
|
2022-11-20 17:38:46 +00:00
|
|
|
def test_address(self):
|
|
|
|
self.assertEqual('@y.z@y.z', self.user.address())
|
|
|
|
|
2023-02-24 13:25:29 +00:00
|
|
|
self.user.actor_as2 = {'type': 'Person'}
|
2022-11-20 17:38:46 +00:00
|
|
|
self.assertEqual('@y.z@y.z', self.user.address())
|
|
|
|
|
2023-02-24 13:25:29 +00:00
|
|
|
self.user.actor_as2 = {'url': 'http://foo'}
|
2022-11-27 00:05:02 +00:00
|
|
|
self.assertEqual('@y.z@y.z', self.user.address())
|
|
|
|
|
2023-02-24 13:25:29 +00:00
|
|
|
self.user.actor_as2 = {'url': ['http://foo', 'acct:bar@foo', 'acct:baz@y.z']}
|
2022-11-27 00:05:02 +00:00
|
|
|
self.assertEqual('@baz@y.z', self.user.address())
|
2022-11-20 17:38:46 +00:00
|
|
|
|
2023-03-17 00:17:11 +00:00
|
|
|
def test_actor_id(self):
|
|
|
|
with app.test_request_context('/'):
|
|
|
|
self.assertEqual('http://localhost/y.z', self.user.actor_id())
|
|
|
|
|
2022-11-28 21:10:51 +00:00
|
|
|
def _test_verify(self, redirects, hcard, actor, redirects_error=None):
|
|
|
|
with app.test_request_context('/'):
|
2022-12-02 18:48:16 +00:00
|
|
|
got = self.user.verify()
|
|
|
|
self.assertEqual(self.user.key, got.key)
|
|
|
|
|
2022-11-28 21:10:51 +00:00
|
|
|
with self.subTest(redirects=redirects, hcard=hcard, actor=actor,
|
|
|
|
redirects_error=redirects_error):
|
|
|
|
self.assert_equals(redirects, bool(self.user.has_redirects))
|
|
|
|
self.assert_equals(hcard, bool(self.user.has_hcard))
|
|
|
|
if actor is None:
|
|
|
|
self.assertIsNone(self.user.actor_as2)
|
|
|
|
else:
|
2023-02-24 13:25:29 +00:00
|
|
|
got = {k: v for k, v in self.user.actor_as2.items()
|
2022-11-28 21:10:51 +00:00
|
|
|
if k in actor}
|
|
|
|
self.assert_equals(actor, got)
|
|
|
|
self.assert_equals(redirects_error, self.user.redirects_error)
|
|
|
|
|
2022-11-19 02:46:27 +00:00
|
|
|
@mock.patch('requests.get')
|
2022-11-28 21:10:51 +00:00
|
|
|
def test_verify_neither(self, mock_get):
|
|
|
|
empty = requests_response('')
|
2022-11-19 02:46:27 +00:00
|
|
|
mock_get.side_effect = [empty, empty]
|
2022-11-28 21:10:51 +00:00
|
|
|
self._test_verify(False, False, None)
|
2022-11-19 02:46:27 +00:00
|
|
|
|
2022-11-28 21:10:51 +00:00
|
|
|
@mock.patch('requests.get')
|
|
|
|
def test_verify_redirect_strips_query_params(self, mock_get):
|
2022-11-19 02:46:27 +00:00
|
|
|
half_redir = requests_response(
|
2022-11-28 21:10:51 +00:00
|
|
|
status=302, redirected_url='http://localhost/.well-known/webfinger')
|
2022-11-19 02:46:27 +00:00
|
|
|
no_hcard = requests_response('<html><body></body></html>')
|
|
|
|
mock_get.side_effect = [half_redir, no_hcard]
|
2022-11-28 21:10:51 +00:00
|
|
|
self._test_verify(False, False, None, """\
|
2022-11-28 01:45:16 +00:00
|
|
|
Current vs expected:<pre>- http://localhost/.well-known/webfinger
|
|
|
|
+ https://fed.brid.gy/.well-known/webfinger?resource=acct:y.z@y.z</pre>""")
|
2022-11-19 02:46:27 +00:00
|
|
|
|
2022-11-28 21:10:51 +00:00
|
|
|
@mock.patch('requests.get')
|
|
|
|
def test_verify_multiple_redirects(self, mock_get):
|
|
|
|
two_redirs = requests_response(
|
|
|
|
status=302, redirected_url=[
|
|
|
|
'https://www.y.z/.well-known/webfinger?resource=acct:y.z@y.z',
|
|
|
|
'http://localhost/.well-known/webfinger?resource=acct:y.z@y.z',
|
|
|
|
])
|
|
|
|
no_hcard = requests_response('<html><body></body></html>')
|
|
|
|
mock_get.side_effect = [two_redirs, no_hcard]
|
|
|
|
self._test_verify(True, False, None)
|
|
|
|
|
2022-12-07 06:37:56 +00:00
|
|
|
@mock.patch('requests.get')
|
|
|
|
def test_verify_redirect_404(self, mock_get):
|
|
|
|
redir_404 = requests_response(status=404, redirected_url='http://this/404s')
|
|
|
|
no_hcard = requests_response('<html><body></body></html>')
|
|
|
|
mock_get.side_effect = [redir_404, no_hcard]
|
|
|
|
self._test_verify(False, False, None, """\
|
2022-12-07 06:41:55 +00:00
|
|
|
<pre>https://y.z/.well-known/webfinger?resource=acct:y.z@y.z
|
2022-12-07 06:37:56 +00:00
|
|
|
redirected to:
|
|
|
|
http://this/404s
|
2022-12-07 06:41:55 +00:00
|
|
|
returned HTTP 404</pre>""")
|
2022-12-07 06:37:56 +00:00
|
|
|
|
2023-02-14 16:25:41 +00:00
|
|
|
@mock.patch('requests.get')
|
|
|
|
def test_verify_no_hcard(self, mock_get):
|
|
|
|
mock_get.side_effect = [
|
|
|
|
self.full_redir,
|
|
|
|
requests_response("""
|
|
|
|
<body>
|
|
|
|
<div class="h-entry">
|
|
|
|
<p class="e-content">foo bar</p>
|
|
|
|
</div>
|
|
|
|
</body>
|
|
|
|
"""),
|
|
|
|
]
|
|
|
|
self._test_verify(True, False, None)
|
|
|
|
|
2022-11-28 21:10:51 +00:00
|
|
|
@mock.patch('requests.get')
|
|
|
|
def test_verify_non_representative_hcard(self, mock_get):
|
2022-11-19 02:46:27 +00:00
|
|
|
bad_hcard = requests_response(
|
2022-11-20 17:38:46 +00:00
|
|
|
'<html><body><a class="h-card u-url" href="https://a.b/">acct:me@y.z</a></body></html>',
|
2022-11-28 21:10:51 +00:00
|
|
|
url='https://y.z/',
|
2022-11-19 02:46:27 +00:00
|
|
|
)
|
2023-02-14 16:25:41 +00:00
|
|
|
mock_get.side_effect = [self.full_redir, bad_hcard]
|
2022-11-28 21:10:51 +00:00
|
|
|
self._test_verify(True, False, None)
|
2022-11-19 02:46:27 +00:00
|
|
|
|
2022-11-28 21:10:51 +00:00
|
|
|
@mock.patch('requests.get')
|
|
|
|
def test_verify_both_work(self, mock_get):
|
2022-11-20 17:38:46 +00:00
|
|
|
hcard = requests_response("""
|
|
|
|
<html><body class="h-card">
|
|
|
|
<a class="u-url p-name" href="/">me</a>
|
|
|
|
<a class="u-url" href="acct:myself@y.z">Masto</a>
|
|
|
|
</body></html>""",
|
2022-11-28 21:10:51 +00:00
|
|
|
url='https://y.z/',
|
2022-11-19 02:46:27 +00:00
|
|
|
)
|
2023-02-14 16:25:41 +00:00
|
|
|
mock_get.side_effect = [self.full_redir, hcard]
|
2022-11-28 21:10:51 +00:00
|
|
|
self._test_verify(True, True, {
|
2022-11-20 17:38:46 +00:00
|
|
|
'type': 'Person',
|
|
|
|
'name': 'me',
|
2022-12-01 05:04:22 +00:00
|
|
|
'url': ['http://localhost/r/https://y.z/', 'acct:myself@y.z'],
|
2022-11-22 22:46:39 +00:00
|
|
|
'preferredUsername': 'y.z',
|
2022-11-20 17:38:46 +00:00
|
|
|
})
|
2022-11-19 02:46:27 +00:00
|
|
|
|
2022-12-02 18:48:16 +00:00
|
|
|
@mock.patch('requests.get')
|
|
|
|
def test_verify_www_redirect(self, mock_get):
|
2023-03-10 23:13:45 +00:00
|
|
|
www_user = self.make_user('www.y.z')
|
2022-12-02 18:48:16 +00:00
|
|
|
|
|
|
|
empty = requests_response('')
|
|
|
|
mock_get.side_effect = [
|
|
|
|
requests_response(status=302, redirected_url='https://www.y.z/'),
|
|
|
|
empty, empty,
|
|
|
|
]
|
|
|
|
|
|
|
|
with app.test_request_context('/'):
|
|
|
|
got = www_user.verify()
|
|
|
|
self.assertEqual('y.z', got.key.id())
|
|
|
|
|
|
|
|
root_user = User.get_by_id('y.z')
|
|
|
|
self.assertEqual(root_user.key, www_user.key.get().use_instead)
|
|
|
|
self.assertEqual(root_user.key, User.get_or_create('www.y.z').key)
|
|
|
|
|
2023-02-14 16:25:41 +00:00
|
|
|
@mock.patch('requests.get')
|
|
|
|
def test_verify_actor_rel_me_links(self, mock_get):
|
|
|
|
mock_get.side_effect = [
|
|
|
|
self.full_redir,
|
|
|
|
requests_response("""
|
|
|
|
<body>
|
|
|
|
<div class="h-card">
|
|
|
|
<a class="u-url" rel="me" href="/about-me">Mrs. ☕ Foo</a>
|
|
|
|
<a class="u-url" rel="me" href="/">should be ignored</a>
|
|
|
|
<a class="u-url" rel="me" href="http://one" title="one title">
|
|
|
|
one text
|
|
|
|
</a>
|
|
|
|
<a class="u-url" rel="me" href="https://two" title=" two title "> </a>
|
|
|
|
</div>
|
|
|
|
</body>
|
|
|
|
""", url='https://y.z/'),
|
|
|
|
]
|
|
|
|
self._test_verify(True, True, {
|
|
|
|
'attachment': [{
|
|
|
|
'type': 'PropertyValue',
|
|
|
|
'name': 'Mrs. ☕ Foo',
|
|
|
|
'value': '<a rel="me" href="https://y.z/about-me">y.z/about-me</a>',
|
|
|
|
}, {
|
|
|
|
'type': 'PropertyValue',
|
|
|
|
'name': 'Web site',
|
|
|
|
'value': '<a rel="me" href="https://y.z/">y.z</a>',
|
|
|
|
}, {
|
|
|
|
'type': 'PropertyValue',
|
|
|
|
'name': 'one text',
|
|
|
|
'value': '<a rel="me" href="http://one">one</a>',
|
|
|
|
}, {
|
|
|
|
'type': 'PropertyValue',
|
|
|
|
'name': 'two title',
|
|
|
|
'value': '<a rel="me" href="https://two">two</a>',
|
|
|
|
}]})
|
|
|
|
|
|
|
|
@mock.patch('requests.get')
|
|
|
|
def test_verify_override_preferredUsername(self, mock_get):
|
|
|
|
mock_get.side_effect = [
|
|
|
|
self.full_redir,
|
|
|
|
requests_response("""
|
|
|
|
<body>
|
|
|
|
<a class="h-card u-url" rel="me" href="/about-me">
|
|
|
|
<span class="p-nickname">Nick</span>
|
|
|
|
</a>
|
|
|
|
</body>
|
|
|
|
""", url='https://y.z/'),
|
|
|
|
]
|
|
|
|
self._test_verify(True, True, {
|
|
|
|
# stays y.z despite user's username. since Mastodon queries Webfinger
|
|
|
|
# for preferredUsername@fed.brid.gy
|
|
|
|
# https://github.com/snarfed/bridgy-fed/issues/77#issuecomment-949955109
|
|
|
|
'preferredUsername': 'y.z',
|
|
|
|
})
|
|
|
|
|
2023-02-12 20:03:27 +00:00
|
|
|
def test_homepage(self):
|
|
|
|
self.assertEqual('https://y.z/', self.user.homepage)
|
|
|
|
|
|
|
|
def test_is_homepage(self):
|
|
|
|
for url in 'y.z', '//y.z', 'http://y.z', 'https://y.z':
|
|
|
|
self.assertTrue(self.user.is_homepage(url), url)
|
|
|
|
|
|
|
|
for url in None, '', 'y', 'z', 'z.z', 'ftp://y.z', 'http://y', '://y.z':
|
|
|
|
self.assertFalse(self.user.is_homepage(url), url)
|
|
|
|
|
2017-10-11 05:42:19 +00:00
|
|
|
|
2023-01-28 15:48:50 +00:00
|
|
|
class ObjectTest(testutil.TestCase):
|
2017-10-12 04:07:47 +00:00
|
|
|
|
2017-10-11 05:42:19 +00:00
|
|
|
def test_proxy_url(self):
|
2021-07-11 23:50:44 +00:00
|
|
|
with app.test_request_context('/'):
|
2023-02-24 13:25:29 +00:00
|
|
|
obj = Object(id='abc', as2={})
|
2023-01-28 15:48:50 +00:00
|
|
|
self.assertEqual('http://localhost/render?id=abc', obj.proxy_url())
|
2023-01-19 06:20:15 +00:00
|
|
|
|
2023-02-07 05:08:52 +00:00
|
|
|
def test_actor_link(self):
|
2023-03-14 13:54:16 +00:00
|
|
|
with app.test_request_context('/'):
|
|
|
|
for expected, as2 in (
|
|
|
|
('href="">', {}),
|
|
|
|
('href="http://foo">foo', {'actor': 'http://foo'}),
|
|
|
|
('href="">Alice', {'actor': {'name': 'Alice'}}),
|
|
|
|
('href="http://foo">Alice', {'actor': {
|
|
|
|
'name': 'Alice',
|
|
|
|
'url': 'http://foo',
|
|
|
|
}}),
|
|
|
|
("""\
|
|
|
|
title="Alice">
|
|
|
|
<img class="profile" src="http://pic" />
|
|
|
|
Alice""", {'actor': {
|
|
|
|
'name': 'Alice',
|
|
|
|
'icon': {'type': 'Image', 'url': 'http://pic'},
|
|
|
|
}}),
|
|
|
|
):
|
2023-02-24 13:25:29 +00:00
|
|
|
obj = Object(id='x', as2=as2)
|
2023-03-14 13:54:16 +00:00
|
|
|
self.assert_multiline_in(expected, obj.actor_link())
|
2023-02-03 16:10:09 +00:00
|
|
|
|
2023-02-07 05:08:52 +00:00
|
|
|
def test_actor_link_user(self):
|
2023-02-24 13:25:29 +00:00
|
|
|
user = User(id='foo.com', actor_as2={"name": "Alice"})
|
2023-02-07 05:08:52 +00:00
|
|
|
obj = Object(id='x', source_protocol='ui', domains=['foo.com'])
|
2023-03-14 13:54:16 +00:00
|
|
|
self.assertIn(
|
|
|
|
'href="/user/foo.com"><img src="" class="profile"> Alice</a>',
|
2023-02-07 05:08:52 +00:00
|
|
|
obj.actor_link(user))
|
|
|
|
|
2023-02-16 16:21:56 +00:00
|
|
|
def test_put_updates_get_object_cache(self):
|
2023-02-24 13:25:29 +00:00
|
|
|
obj = Object(id='x', as2={})
|
2023-02-16 16:21:56 +00:00
|
|
|
obj.put()
|
2023-03-08 21:10:41 +00:00
|
|
|
key = Protocol.get_object.cache_key(Protocol, 'x')
|
|
|
|
self.assert_entities_equal(obj, Protocol.get_object.cache[key])
|
2023-02-16 16:21:56 +00:00
|
|
|
|
2023-02-16 20:20:21 +00:00
|
|
|
def test_put_fragment_id_doesnt_update_get_object_cache(self):
|
2023-02-24 13:25:29 +00:00
|
|
|
obj = Object(id='x#y', as2={})
|
2023-02-16 20:20:21 +00:00
|
|
|
obj.put()
|
|
|
|
|
2023-03-08 21:10:41 +00:00
|
|
|
self.assertNotIn(Protocol.get_object.cache_key(Protocol, 'x#y'),
|
|
|
|
Protocol.get_object.cache)
|
|
|
|
self.assertNotIn(Protocol.get_object.cache_key(Protocol, 'x'),
|
|
|
|
Protocol.get_object.cache)
|
2023-02-16 20:20:21 +00:00
|
|
|
|
2023-02-25 03:59:12 +00:00
|
|
|
def test_computed_properties_without_as1(self):
|
|
|
|
Object(id='a').put()
|
|
|
|
|
2023-01-19 06:20:15 +00:00
|
|
|
|
|
|
|
class FollowerTest(testutil.TestCase):
|
2023-01-19 14:49:39 +00:00
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
super().setUp()
|
|
|
|
self.inbound = Follower(dest='foo.com', src='http://bar/@baz',
|
2023-02-24 13:25:29 +00:00
|
|
|
last_follow={'actor': ACTOR})
|
2023-01-19 14:49:39 +00:00
|
|
|
self.outbound = Follower(dest='http://bar/@baz', src='foo.com',
|
2023-02-24 13:25:29 +00:00
|
|
|
last_follow={'object': ACTOR})
|
2023-01-19 14:49:39 +00:00
|
|
|
|
2023-01-19 06:20:15 +00:00
|
|
|
def test_to_as1(self):
|
2023-01-19 14:49:39 +00:00
|
|
|
self.assertEqual({}, Follower().to_as1())
|
2023-01-19 06:20:15 +00:00
|
|
|
|
|
|
|
as1_actor = as2.to_as1(ACTOR)
|
2023-01-19 14:49:39 +00:00
|
|
|
self.assertEqual(as1_actor, self.inbound.to_as1())
|
|
|
|
self.assertEqual(as1_actor, self.outbound.to_as1())
|
2023-01-19 06:20:15 +00:00
|
|
|
|
2023-01-19 14:49:39 +00:00
|
|
|
def test_to_as2(self):
|
|
|
|
self.assertIsNone(Follower().to_as2())
|
|
|
|
self.assertEqual(ACTOR, self.inbound.to_as2())
|
|
|
|
self.assertEqual(ACTOR, self.outbound.to_as2())
|