2023-03-08 21:10:41 +00:00
|
|
|
"""Common test utility code."""
|
2017-10-20 14:13:04 +00:00
|
|
|
import copy
|
2022-11-24 16:20:04 +00:00
|
|
|
import datetime
|
2017-08-24 06:24:47 +00:00
|
|
|
import unittest
|
2019-12-26 06:20:57 +00:00
|
|
|
from unittest.mock import ANY, call
|
2017-08-24 06:24:47 +00:00
|
|
|
|
2023-03-20 21:28:14 +00:00
|
|
|
from flask import g
|
2023-03-27 21:12:06 +00:00
|
|
|
from google.cloud import ndb
|
2023-01-07 17:18:11 +00:00
|
|
|
from granary import as2
|
2023-02-09 04:22:16 +00:00
|
|
|
from granary.tests.test_as1 import (
|
|
|
|
COMMENT,
|
|
|
|
MENTION,
|
|
|
|
NOTE,
|
|
|
|
)
|
2023-03-27 21:12:06 +00:00
|
|
|
import logging
|
2017-10-20 14:13:04 +00:00
|
|
|
from oauth_dropins.webutil import testutil, util
|
2019-12-26 06:20:57 +00:00
|
|
|
from oauth_dropins.webutil.appengine_config import ndb_client
|
2023-01-07 17:18:11 +00:00
|
|
|
from oauth_dropins.webutil.testutil import requests_response
|
2019-12-26 06:20:57 +00:00
|
|
|
import requests
|
2017-08-24 06:24:47 +00:00
|
|
|
|
2023-01-07 17:18:11 +00:00
|
|
|
from app import app, cache
|
2023-02-15 18:57:11 +00:00
|
|
|
import activitypub, common
|
2023-03-27 21:12:06 +00:00
|
|
|
from models import Object, PROTOCOLS, Target, User
|
2023-03-08 21:10:41 +00:00
|
|
|
import protocol
|
2017-08-24 06:24:47 +00:00
|
|
|
|
2023-03-27 21:12:06 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2023-03-10 23:13:45 +00:00
|
|
|
# used in TestCase.make_user() to reuse RSA keys across Users
|
|
|
|
with ndb_client.context():
|
2023-03-19 22:43:55 +00:00
|
|
|
global_user = User.get_or_create('user.com')
|
2017-10-10 14:42:10 +00:00
|
|
|
|
2023-03-14 00:23:57 +00:00
|
|
|
|
2023-03-27 21:12:06 +00:00
|
|
|
Object.source_protocol = ndb.StringProperty(choices=PROTOCOLS + ('fake',))
|
|
|
|
|
2023-03-14 00:23:57 +00:00
|
|
|
class FakeProtocol(protocol.Protocol):
|
|
|
|
LABEL = 'fake'
|
|
|
|
|
2023-03-27 21:12:06 +00:00
|
|
|
# maps string ids to dict AS1 objects. send adds objects here, fetch
|
|
|
|
# returns them
|
|
|
|
objects = {}
|
|
|
|
|
|
|
|
# in-order list of (Object, str URL)
|
|
|
|
sent = []
|
|
|
|
|
|
|
|
# in-order list of ids
|
|
|
|
fetched = []
|
|
|
|
|
2023-03-14 00:23:57 +00:00
|
|
|
@classmethod
|
2023-03-21 02:25:05 +00:00
|
|
|
def send(cls, obj, url, log_data=True):
|
2023-03-27 21:12:06 +00:00
|
|
|
logger.info(f'FakeProtocol.send {url}')
|
2023-04-03 14:53:15 +00:00
|
|
|
cls.sent.append((obj, url))
|
2023-03-27 21:12:06 +00:00
|
|
|
cls.objects[obj.key.id()] = obj
|
2023-03-14 00:23:57 +00:00
|
|
|
|
|
|
|
@classmethod
|
2023-04-03 14:53:15 +00:00
|
|
|
def fetch(cls, obj):
|
|
|
|
id = obj.key.id()
|
|
|
|
logger.info(f'FakeProtocol.load {id}')
|
2023-03-27 21:12:06 +00:00
|
|
|
cls.fetched.append(id)
|
|
|
|
|
|
|
|
if id in cls.objects:
|
2023-04-03 14:53:15 +00:00
|
|
|
obj.our_as1 = cls.objects[id]
|
|
|
|
return obj
|
2023-03-27 21:12:06 +00:00
|
|
|
|
|
|
|
raise requests.HTTPError(response=util.Struct(status_code='410'))
|
2023-03-08 21:10:41 +00:00
|
|
|
|
2023-03-10 23:13:45 +00:00
|
|
|
|
2017-10-10 14:42:10 +00:00
|
|
|
class TestCase(unittest.TestCase, testutil.Asserts):
|
2017-08-24 06:24:47 +00:00
|
|
|
maxDiff = None
|
|
|
|
|
|
|
|
def setUp(self):
|
2021-07-11 23:30:14 +00:00
|
|
|
super().setUp()
|
2023-03-27 21:12:06 +00:00
|
|
|
|
2021-08-18 14:59:52 +00:00
|
|
|
app.testing = True
|
|
|
|
cache.clear()
|
2023-03-08 21:10:41 +00:00
|
|
|
protocol.seen_ids.clear()
|
2023-04-03 03:36:23 +00:00
|
|
|
protocol.objects_cache.clear()
|
2023-03-11 06:24:58 +00:00
|
|
|
common.webmention_discover.cache.clear()
|
2023-02-09 04:22:16 +00:00
|
|
|
|
2023-03-27 21:12:06 +00:00
|
|
|
FakeProtocol.objects = {}
|
|
|
|
FakeProtocol.sent = []
|
|
|
|
FakeProtocol.fetched = []
|
|
|
|
|
2021-08-18 14:59:52 +00:00
|
|
|
self.client = app.test_client()
|
2023-02-09 04:22:16 +00:00
|
|
|
self.client.__enter__()
|
2019-12-26 06:20:57 +00:00
|
|
|
|
|
|
|
# clear datastore
|
2023-01-24 20:17:24 +00:00
|
|
|
requests.post(f'http://{ndb_client.host}/reset')
|
2019-12-26 06:20:57 +00:00
|
|
|
self.ndb_context = ndb_client.context()
|
|
|
|
self.ndb_context.__enter__()
|
2017-08-24 06:24:47 +00:00
|
|
|
|
2023-01-13 03:43:12 +00:00
|
|
|
util.now = lambda **kwargs: testutil.NOW
|
|
|
|
|
2017-08-24 06:24:47 +00:00
|
|
|
def tearDown(self):
|
2019-12-26 06:20:57 +00:00
|
|
|
self.ndb_context.__exit__(None, None, None)
|
2023-02-09 04:22:16 +00:00
|
|
|
self.client.__exit__(None, None, None)
|
2021-07-11 23:30:14 +00:00
|
|
|
super().tearDown()
|
2017-10-20 14:13:04 +00:00
|
|
|
|
2023-03-10 23:13:45 +00:00
|
|
|
@staticmethod
|
|
|
|
def make_user(domain, **kwargs):
|
|
|
|
"""Reuse RSA key across Users because generating it is expensive."""
|
|
|
|
user = User(id=domain,
|
|
|
|
mod=global_user.mod,
|
|
|
|
public_exponent=global_user.public_exponent,
|
|
|
|
private_exponent=global_user.private_exponent,
|
|
|
|
**kwargs)
|
|
|
|
user.put()
|
|
|
|
return user
|
|
|
|
|
2023-02-09 04:22:16 +00:00
|
|
|
@staticmethod
|
|
|
|
def add_objects():
|
2023-02-24 03:17:26 +00:00
|
|
|
with app.test_request_context('/'):
|
|
|
|
# post
|
2023-03-19 22:43:55 +00:00
|
|
|
Object(id='a', domains=['user.com'], labels=['feed', 'notification'],
|
2023-02-24 13:25:29 +00:00
|
|
|
as2=as2.from_as1(NOTE)).put()
|
2023-02-24 03:17:26 +00:00
|
|
|
# different domain
|
2023-03-19 22:43:55 +00:00
|
|
|
Object(id='b', domains=['nope.org'], labels=['feed', 'notification'],
|
2023-02-24 13:25:29 +00:00
|
|
|
as2=as2.from_as1(MENTION)).put()
|
2023-02-24 03:17:26 +00:00
|
|
|
# reply
|
2023-03-19 22:43:55 +00:00
|
|
|
Object(id='d', domains=['user.com'], labels=['feed', 'notification'],
|
2023-02-24 13:25:29 +00:00
|
|
|
as2=as2.from_as1(COMMENT)).put()
|
2023-02-24 03:17:26 +00:00
|
|
|
# not feed/notif
|
2023-03-19 22:43:55 +00:00
|
|
|
Object(id='e', domains=['user.com'],
|
2023-02-24 13:25:29 +00:00
|
|
|
as2=as2.from_as1(NOTE)).put()
|
2023-02-24 03:17:26 +00:00
|
|
|
# deleted
|
2023-03-19 22:43:55 +00:00
|
|
|
Object(id='f', domains=['user.com'], labels=['feed', 'notification', 'user'],
|
2023-02-24 13:25:29 +00:00
|
|
|
as2=as2.from_as1(NOTE), deleted=True).put()
|
2023-02-09 04:22:16 +00:00
|
|
|
|
2017-10-20 14:13:04 +00:00
|
|
|
def req(self, url, **kwargs):
|
|
|
|
"""Returns a mock requests call."""
|
2022-11-24 16:20:04 +00:00
|
|
|
kwargs.setdefault('headers', {}).update({
|
|
|
|
'User-Agent': util.user_agent,
|
|
|
|
})
|
2017-10-20 14:13:04 +00:00
|
|
|
kwargs.setdefault('timeout', util.HTTP_TIMEOUT)
|
2019-10-04 04:08:26 +00:00
|
|
|
kwargs.setdefault('stream', True)
|
2017-10-20 14:13:04 +00:00
|
|
|
return call(url, **kwargs)
|
2022-03-17 04:11:09 +00:00
|
|
|
|
2022-11-24 16:20:04 +00:00
|
|
|
def as2_req(self, url, **kwargs):
|
|
|
|
headers = {
|
2023-01-16 21:00:38 +00:00
|
|
|
'Date': 'Sun, 02 Jan 2022 03:04:05 GMT',
|
2022-11-24 16:20:04 +00:00
|
|
|
'Host': util.domain_from_link(url, minimize=False),
|
2022-11-24 17:39:01 +00:00
|
|
|
'Content-Type': 'application/activity+json',
|
|
|
|
'Digest': ANY,
|
2023-03-08 21:10:41 +00:00
|
|
|
**activitypub.CONNEG_HEADERS_AS2_HTML,
|
2022-11-24 16:20:04 +00:00
|
|
|
**kwargs.pop('headers', {}),
|
|
|
|
}
|
2023-02-07 03:23:25 +00:00
|
|
|
return self.req(url, data=None, auth=ANY, headers=headers,
|
|
|
|
allow_redirects=False, **kwargs)
|
|
|
|
|
2023-01-07 17:18:11 +00:00
|
|
|
def as2_resp(self, obj):
|
|
|
|
return requests_response(obj, content_type=as2.CONTENT_TYPE)
|
|
|
|
|
2022-03-17 04:11:09 +00:00
|
|
|
def assert_req(self, mock, url, **kwargs):
|
|
|
|
"""Checks a mock requests call."""
|
2022-11-24 17:39:01 +00:00
|
|
|
kwargs.setdefault('headers', {}).setdefault(
|
|
|
|
'User-Agent', 'Bridgy Fed (https://fed.brid.gy/)')
|
2022-03-17 04:11:09 +00:00
|
|
|
kwargs.setdefault('stream', True)
|
|
|
|
kwargs.setdefault('timeout', util.HTTP_TIMEOUT)
|
|
|
|
mock.assert_any_call(url, **kwargs)
|
2023-01-29 22:13:58 +00:00
|
|
|
|
2023-03-21 02:17:55 +00:00
|
|
|
def assert_object(self, id, delivered_protocol=None, **props):
|
2023-01-29 22:13:58 +00:00
|
|
|
got = Object.get_by_id(id)
|
|
|
|
assert got, id
|
|
|
|
|
2023-02-01 21:19:41 +00:00
|
|
|
# right now we only do ActivityPub
|
|
|
|
for field in 'delivered', 'undelivered', 'failed':
|
2023-03-21 02:17:55 +00:00
|
|
|
props[field] = [Target(uri=uri, protocol=delivered_protocol)
|
2023-02-01 21:19:41 +00:00
|
|
|
for uri in props.get(field, [])]
|
|
|
|
|
2023-02-24 03:17:26 +00:00
|
|
|
mf2 = props.get('mf2')
|
|
|
|
if mf2 and 'items' in mf2:
|
|
|
|
props['mf2'] = mf2['items'][0]
|
|
|
|
|
2023-02-14 05:43:49 +00:00
|
|
|
for computed in 'type', 'object_ids':
|
|
|
|
val = props.pop(computed, None)
|
|
|
|
if val is not None:
|
2023-03-19 23:21:44 +00:00
|
|
|
self.assertEqual(val, getattr(got, computed), computed)
|
2023-02-14 05:43:49 +00:00
|
|
|
|
2023-02-24 03:17:26 +00:00
|
|
|
if expected_as1 := props.pop('as1', None):
|
|
|
|
self.assert_equals(common.redirect_unwrap(expected_as1), got.as1)
|
|
|
|
|
2023-04-02 02:13:51 +00:00
|
|
|
if got.mf2:
|
|
|
|
got.mf2.pop('url', None)
|
|
|
|
|
2023-01-29 22:13:58 +00:00
|
|
|
self.assert_entities_equal(Object(id=id, **props), got,
|
2023-04-02 02:13:51 +00:00
|
|
|
ignore=['as1', 'created', 'object_ids',
|
|
|
|
'type', 'updated'])
|
|
|
|
|
|
|
|
def assert_equals(self, expected, actual, msg=None, ignore=(), **kwargs):
|
|
|
|
return super().assert_equals(
|
|
|
|
expected, actual, msg=msg, ignore=tuple(ignore) + ('@context',), **kwargs)
|