diff --git a/activitypub.py b/activitypub.py index c56abc2..28a586a 100644 --- a/activitypub.py +++ b/activitypub.py @@ -28,7 +28,7 @@ from common import ( redirect_wrap, TLD_BLOCKLIST, ) -from models import Follower, Object, PROTOCOLS, Target, User +from models import Follower, Object, PROTOCOLS, User from protocol import Protocol # TODO: remove this. we only need it to make sure Web is registered in PROTOCOLS @@ -263,8 +263,8 @@ class ActivityPub(User, Protocol): key_actor = cls.load(keyId) except BadGateway: obj_id = as1.get_object(activity).get('id') - if (activity.get('type') == 'Delete' and obj_id and - keyId == fragmentless(obj_id)): + if (activity.get('type') == 'Delete' and obj_id + and keyId == fragmentless(obj_id)): logger.info('Object/actor being deleted is also keyId') key_actor = Object(id=keyId, source_protocol='activitypub', deleted=True) key_actor.put() @@ -356,12 +356,13 @@ def signed_request(fn, url, data=None, log_data=True, headers=None, **kwargs): # handle GET redirects manually so that we generate a new HTTP signature if resp.is_redirect and fn == util.requests_get: - return signed_request(fn, resp.headers['Location'], data=data, - headers=headers, log_data=log_data, **kwargs) + return signed_request(fn, resp.headers['Location'], data=data, + headers=headers, log_data=log_data, **kwargs) type = common.content_type(resp) if (type and type != 'text/html' and - (type.startswith('text/') or type.endswith('+json') or type.endswith('/json'))): + (type.startswith('text/') or type.endswith('+json') + or type.endswith('/json'))): logger.info(resp.text) return resp @@ -546,7 +547,7 @@ def postprocess_as2_actor(actor, wrap=True): url = g.user.web_url() if g.user else None urls = util.get_list(actor, 'url') if not urls and url: - urls = [url] + urls = [url] domain = util.domain_from_link(urls[0], minimize=False) if wrap: @@ -565,11 +566,11 @@ def postprocess_as2_actor(actor, wrap=True): # Override the label for their home page to be "Web site" for att in util.get_list(actor, 'attachment'): - if att.get('type') == 'PropertyValue': - val = att.get('value', '') - link = util.parse_html(val).find('a') - if url and (val == url or link.get('href') == url): - att['name'] = 'Web site' + if att.get('type') == 'PropertyValue': + val = att.get('value', '') + link = util.parse_html(val).find('a') + if url and (val == url or link.get('href') == url): + att['name'] = 'Web site' # required by pixelfed. https://github.com/snarfed/bridgy-fed/issues/39 actor.setdefault('summary', '') @@ -591,7 +592,7 @@ def actor(protocol, domain): if not g.user: try: obj = cls.load(f'https://{domain}/', gateway=True) - except NoMicroformats as e: + except NoMicroformats: obj = None g.user = cls.get_or_create(id=domain, obj=obj) diff --git a/appengine_config.py b/appengine_config.py index 5b96eae..d6cb616 100644 --- a/appengine_config.py +++ b/appengine_config.py @@ -1,16 +1,19 @@ """Bridgy App Engine config. """ -# suppress these INFO logs: -# Sandbox prevented access to file "/usr/local/Caskroom/google-cloud-sdk" -# If it is a static file, check that `application_readable: true` is set in your app.yaml import logging + class StubsFilter(logging.Filter): - def filter(self, record): - msg = record.getMessage() - if (msg.startswith('Sandbox prevented access to file') or - msg.startswith('If it is a static file, check that')): - return 0 - return 1 + """Suppress these INFO logs: + Sandbox prevented access to file "/usr/local/Caskroom/google-cloud-sdk" + If it is a static file, check that `application_readable: true` is set in your app.yaml + """ + def filter(self, record): + msg = record.getMessage() + if (msg.startswith('Sandbox prevented access to file') + or msg.startswith('If it is a static file, check that')): + return 0 + return 1 + logging.getLogger().addFilter(StubsFilter()) diff --git a/common.py b/common.py index b9f69fa..f67fb5f 100644 --- a/common.py +++ b/common.py @@ -2,7 +2,6 @@ """Misc common utilities. """ import base64 -import copy from datetime import timedelta import logging import re @@ -12,11 +11,8 @@ import urllib.parse import cachetools from Crypto.Util import number from flask import abort, g, make_response, request -from granary import as1, as2, microformats2 -import mf2util from oauth_dropins.webutil import util, webmention from oauth_dropins.webutil.appengine_info import DEBUG -from oauth_dropins.webutil.util import json_dumps, json_loads from werkzeug.exceptions import BadRequest logger = logging.getLogger(__name__) @@ -90,10 +86,10 @@ def long_to_base64(x): def host_url(path_query=None): base = request.host_url - if (util.domain_or_parent_in(request.host, OTHER_DOMAINS) or - # when running locally against prod datastore - (not DEBUG and request.host in LOCAL_DOMAINS)): - base = f'https://{PRIMARY_DOMAIN}' + if (util.domain_or_parent_in(request.host, OTHER_DOMAINS) + # when running locally against prod datastore + or (not DEBUG and request.host in LOCAL_DOMAINS)): + base = f'https://{PRIMARY_DOMAIN}' return urllib.parse.urljoin(base, path_query) @@ -105,26 +101,26 @@ def error(msg, status=400, exc_info=None, **kwargs): def pretty_link(url, text=None, **kwargs): - """Wrapper around util.pretty_link() that converts Mastodon user URLs to @-@. + """Wrapper around util.pretty_link() that converts Mastodon user URLs to @-@. - Eg for URLs like https://mastodon.social/@foo and - https://mastodon.social/users/foo, defaults text to @foo@mastodon.social if - it's not provided. + Eg for URLs like https://mastodon.social/@foo and + https://mastodon.social/users/foo, defaults text to @foo@mastodon.social if + it's not provided. - Args: - url: str - text: str - kwargs: passed through to :func:`webutil.util.pretty_link` - """ - if g.user and g.user.is_web_url(url): - return g.user.user_page_link() + Args: + url: str + text: str + kwargs: passed through to :func:`webutil.util.pretty_link` + """ + if g.user and g.user.is_web_url(url): + return g.user.user_page_link() - if text is None: - match = re.match(r'https?://([^/]+)/(@|users/)([^/]+)$', url) - if match: - text = match.expand(r'@\3@\1') + if text is None: + match = re.match(r'https?://([^/]+)/(@|users/)([^/]+)$', url) + if match: + text = match.expand(r'@\3@\1') - return util.pretty_link(url, text=text, **kwargs) + return util.pretty_link(url, text=text, **kwargs) def content_type(resp): @@ -199,25 +195,25 @@ def redirect_unwrap(val): def webmention_endpoint_cache_key(url): - """Returns cache key for a cached webmention endpoint for a given URL. + """Returns cache key for a cached webmention endpoint for a given URL. - Just the domain by default. If the URL is the home page, ie path is / , the - key includes a / at the end, so that we cache webmention endpoints for home - pages separate from other pages. https://github.com/snarfed/bridgy/issues/701 + Just the domain by default. If the URL is the home page, ie path is / , the + key includes a / at the end, so that we cache webmention endpoints for home + pages separate from other pages. https://github.com/snarfed/bridgy/issues/701 - Example: 'snarfed.org /' + Example: 'snarfed.org /' - https://github.com/snarfed/bridgy-fed/issues/423 + https://github.com/snarfed/bridgy-fed/issues/423 - Adapted from bridgy/util.py. - """ - parsed = urllib.parse.urlparse(url) - key = parsed.netloc - if parsed.path in ('', '/'): - key += ' /' + Adapted from bridgy/util.py. + """ + parsed = urllib.parse.urlparse(url) + key = parsed.netloc + if parsed.path in ('', '/'): + key += ' /' - # logger.debug(f'wm cache key {key}') - return key + # logger.debug(f'wm cache key {key}') + return key @cachetools.cached(cachetools.TTLCache(50000, 60 * 60 * 2), # 2h expiration diff --git a/config.py b/config.py index 0ef321e..617ec29 100644 --- a/config.py +++ b/config.py @@ -14,10 +14,10 @@ SESSION_COOKIE_SAMESITE = 'Lax' CACHE_THRESHOLD = 3000 if appengine_info.DEBUG: - ENV = 'development' - CACHE_TYPE = 'NullCache' - SECRET_KEY = 'sooper seekret' + ENV = 'development' + CACHE_TYPE = 'NullCache' + SECRET_KEY = 'sooper seekret' else: - ENV = 'production' - CACHE_TYPE = 'SimpleCache' - SECRET_KEY = util.read('flask_secret_key') + ENV = 'production' + CACHE_TYPE = 'SimpleCache' + SECRET_KEY = util.read('flask_secret_key') diff --git a/convert.py b/convert.py index 6377a6a..16d2557 100644 --- a/convert.py +++ b/convert.py @@ -5,7 +5,6 @@ constants from the :class:`Protocol` subclasses. """ import logging import re -import urllib.parse from flask import g, redirect, request from granary import as1 @@ -78,8 +77,8 @@ def convert(dest, _): if obj_id: # TODO: PROTOCOLS[src].load() this instead? obj_obj = Object.get_by_id(obj_id) - if (obj_obj and obj_obj.as1 and - not obj_obj.as1.keys() <= set(['id', 'url', 'objectType'])): + if (obj_obj and obj_obj.as1 + and not obj_obj.as1.keys() <= set(['id', 'url', 'objectType'])): logger.info(f'{type} activity, redirecting to Object {obj_id}') return redirect(f'/{path_prefix}{obj_id}', code=301) diff --git a/flask_app.py b/flask_app.py index 5baadce..5dd6e6f 100644 --- a/flask_app.py +++ b/flask_app.py @@ -15,8 +15,6 @@ from oauth_dropins.webutil import ( util, ) -import common - logger = logging.getLogger(__name__) logging.getLogger('lexrpc').setLevel(logging.INFO) logging.getLogger('negotiator').setLevel(logging.WARNING) @@ -34,6 +32,7 @@ app.register_error_handler(Exception, flask_util.handle_exception) if appengine_info.LOCAL: flask_gae_static.init_app(app) + @app.before_request def init_globals(): """Set request globals. @@ -42,6 +41,7 @@ def init_globals(): """ g.user = None + # don't redirect API requests with blank path elements app.url_map.merge_slashes = False app.url_map.redirect_defaults = False diff --git a/follow.py b/follow.py index 9e7ff5b..319473a 100644 --- a/follow.py +++ b/follow.py @@ -5,16 +5,13 @@ https://socialhub.activitypub.rocks/t/what-is-the-current-spec-for-remote-follow https://www.rfc-editor.org/rfc/rfc7033 """ import logging -import urllib.parse from flask import g, redirect, request, session from granary import as2 from oauth_dropins import indieauth -from oauth_dropins.webutil import flask_util, util -from oauth_dropins.webutil.flask_util import error, flash from oauth_dropins.webutil import util +from oauth_dropins.webutil.flask_util import error, flash from oauth_dropins.webutil.testutil import NOW -from oauth_dropins.webutil.util import json_dumps, json_loads from activitypub import ActivityPub from flask_app import app diff --git a/models.py b/models.py index 2690528..961b7f2 100644 --- a/models.py +++ b/models.py @@ -7,7 +7,6 @@ import random import urllib.parse from arroba.mst import dag_cbor_cid -from Crypto import Random from Crypto.PublicKey import ECC, RSA import dag_json from flask import g, request @@ -18,10 +17,9 @@ from oauth_dropins.webutil.appengine_info import DEBUG from oauth_dropins.webutil.flask_util import error from oauth_dropins.webutil.models import ComputedJsonProperty, JsonProperty, StringIdModel from oauth_dropins.webutil.util import json_dumps, json_loads -import requests import common -from common import base64_to_long, long_to_base64, redirect_unwrap, redirect_wrap +from common import base64_to_long, long_to_base64, redirect_unwrap # maps string label to Protocol subclass. populated by ProtocolUserMeta. # seed with old and upcoming protocols that don't have their own classes (yet). @@ -475,8 +473,8 @@ class Object(StringIdModel): """Returns a pretty actor link with their name and profile picture.""" attrs = {'class': 'h-card u-author'} - if (self.source_protocol in ('web', 'webmention', 'ui') and g.user and - (g.user.key in self.users or g.user.key.id() in self.domains)): + if (self.source_protocol in ('web', 'webmention', 'ui') and g.user + and (g.user.key in self.users or g.user.key.id() in self.domains)): # outbound; show a nice link to the user return g.user.user_page_link() @@ -498,6 +496,7 @@ class Object(StringIdModel): {util.ellipsize(name, chars=40)} """ + class AtpNode(StringIdModel): """An AT Protocol (Bluesky) node. diff --git a/pages.py b/pages.py index ad807ac..a8f9c1b 100644 --- a/pages.py +++ b/pages.py @@ -1,26 +1,20 @@ """UI pages.""" -import calendar import datetime import logging import os -import re -import urllib.parse -from flask import g, redirect, render_template, request -from google.cloud.ndb.model import get_multi +from flask import g, render_template, request from google.cloud.ndb.query import OR from google.cloud.ndb.stats import KindStat from granary import as1, as2, atom, microformats2, rss import humanize from oauth_dropins.webutil import flask_util, logs, util -from oauth_dropins.webutil.flask_util import error, flash, redirect -from oauth_dropins.webutil.util import json_dumps, json_loads +from oauth_dropins.webutil.flask_util import error, redirect import common from common import DOMAIN_RE from flask_app import app, cache -from models import fetch_page, Follower, Object, PAGE_SIZE, PROTOCOLS, User -from web import Web +from models import fetch_page, Follower, Object, PAGE_SIZE, PROTOCOLS FOLLOWERS_UI_LIMIT = 999 @@ -192,7 +186,6 @@ def fetch_objects(query): to fetch the previous and next pages, respectively """ objects, new_before, new_after = fetch_page(query, Object) - seen = set() # synthesize human-friendly content for objects for i, obj in enumerate(objects): diff --git a/protocol.py b/protocol.py index dacf5eb..c280d3b 100644 --- a/protocol.py +++ b/protocol.py @@ -3,18 +3,17 @@ import logging import threading from urllib.parse import urljoin -from cachetools import cached, LRUCache +from cachetools import LRUCache from flask import g, request from google.cloud import ndb from google.cloud.ndb import OR -from granary import as1, as2 -import requests +from granary import as1 import werkzeug.exceptions import common from common import error from models import Follower, Object, PROTOCOLS, Target -from oauth_dropins.webutil import util, webmention +from oauth_dropins.webutil import util from oauth_dropins.webutil.util import json_dumps, json_loads SUPPORTED_TYPES = ( @@ -433,8 +432,8 @@ class Protocol: # deliver original posts and reposts to followers is_reply = (obj.type == 'comment' or (inner_obj and inner_obj.get('inReplyTo'))) - if (actor and actor_id and - (obj.type == 'share' or obj.type in ('create', 'post') and not is_reply)): + if ((obj.type == 'share' or obj.type in ('create', 'post') and not is_reply) + and actor and actor_id): logger.info(f'Delivering to followers of {actor_id}') for f in Follower.query(Follower.to == from_cls(id=actor_id).key, Follower.status == 'active'): @@ -580,11 +579,11 @@ class Protocol: if 'notification' not in obj.labels: obj.labels.append('notification') except BaseException as e: - code, body = util.interpret_http_exception(e) - if not code and not body: - raise - errors.append((code, body)) - obj.failed.append(target) + code, body = util.interpret_http_exception(e) + if not code and not body: + raise + errors.append((code, body)) + obj.failed.append(target) obj.put() diff --git a/redirect.py b/redirect.py index 99ba1b2..b81fe51 100644 --- a/redirect.py +++ b/redirect.py @@ -18,7 +18,7 @@ import logging import re import urllib.parse -from flask import g, redirect, request +from flask import g, request from granary import as2 from negotiator import ContentNegotiator, AcceptParameters, ContentType from oauth_dropins.webutil import flask_util, util @@ -28,7 +28,6 @@ from oauth_dropins.webutil.util import json_dumps, json_loads from activitypub import ActivityPub from flask_app import app, cache from common import CACHE_TIME, CONTENT_TYPE_HTML -from models import Object, User from web import Web logger = logging.getLogger(__name__) diff --git a/tests/test_activitypub.py b/tests/test_activitypub.py index db57082..f19f52a 100644 --- a/tests/test_activitypub.py +++ b/tests/test_activitypub.py @@ -6,14 +6,12 @@ from datetime import datetime, timedelta from hashlib import sha256 import logging from unittest import skip -from unittest.mock import ANY, call, patch -import urllib.parse +from unittest.mock import patch from flask import g from google.cloud import ndb from granary import as2, microformats2 from httpsig import HeaderSigner -from oauth_dropins.webutil import util from oauth_dropins.webutil.testutil import requests_response from oauth_dropins.webutil.util import json_dumps, json_loads import requests @@ -26,10 +24,8 @@ from .testutil import Fake, TestCase import activitypub from activitypub import ActivityPub, postprocess_as2 import common -import models from models import Follower, Object import protocol -from protocol import Protocol from web import Web # have to import module, not attrs, to avoid circular import @@ -49,7 +45,7 @@ ACTOR_BASE = { 'https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1', ], - 'type' : 'Person', + 'type': 'Person', 'id': 'http://localhost/user.com', 'url': 'http://localhost/r/https://user.com/', 'preferredUsername': 'user.com', @@ -387,7 +383,7 @@ class ActivityPubTest(TestCase): **REPLY, 'actor': LIKE_ACTOR, } - got = self._test_inbox_reply(reply, { + self._test_inbox_reply(reply, { 'as2': reply, 'type': 'post', 'labels': ['activity', 'notification'], @@ -749,7 +745,6 @@ class ActivityPubTest(TestCase): self.assert_user(ActivityPub, 'https://user.com/actor', obj_as2=LIKE_ACTOR, direct=True) - def test_inbox_follow_accept_with_id(self, *mocks): self._test_inbox_follow_accept(FOLLOW_WRAPPED, ACCEPT, *mocks) @@ -768,10 +763,6 @@ class ActivityPubTest(TestCase): object_ids=[FOLLOW['object']]) def test_inbox_follow_accept_with_object(self, *mocks): - wrapped_user = { - 'id': FOLLOW_WRAPPED['object'], - 'url': FOLLOW_WRAPPED['object'], - } unwrapped_user = { 'id': FOLLOW['object'], 'url': FOLLOW['object'], @@ -905,7 +896,6 @@ class ActivityPubTest(TestCase): self.assertEqual('https://mas.to/users/swentel#followed-https://user.com/', follower.follow.get().as2['url']) - def test_inbox_undo_follow(self, mock_head, mock_get, mock_post): follower = Follower(to=self.user.key, from_=ActivityPub.get_or_create(ACTOR['id']).key, @@ -1065,7 +1055,6 @@ class ActivityPubTest(TestCase): # invalid signature, header changed protocol.seen_ids.clear() obj_key.delete() - orig_date = headers['Date'] resp = self.client.post('/ap/sharedInbox', data=body, headers={**headers, 'Date': 'X'}) self.assertEqual(401, resp.status_code) @@ -1505,7 +1494,7 @@ class ActivityPubUtilsTest(TestCase): }, postprocess_as2({ 'tag': [ {'name': 'bar', 'href': 'bar'}, - {'type': 'Tag','name': '#baz'}, + {'type': 'Tag', 'name': '#baz'}, # should leave alone {'type': 'Mention', 'href': 'foo'}, ], @@ -1644,7 +1633,7 @@ class ActivityPubUtilsTest(TestCase): allow_redirects=False), requests_response(status=200, allow_redirects=False), ] - resp = activitypub.signed_get('https://first') + activitypub.signed_get('https://first') first = mock_get.call_args_list[0][1] second = mock_get.call_args_list[1][1] @@ -1697,7 +1686,7 @@ class ActivityPubUtilsTest(TestCase): mock_get.assert_has_calls(( self.as2_req('http://orig'), - self.as2_req('http://as2', headers=common.as2.CONNEG_HEADERS), + self.as2_req('http://as2', headers=as2.CONNEG_HEADERS), )) @patch('requests.get') @@ -1708,7 +1697,7 @@ class ActivityPubUtilsTest(TestCase): @patch('requests.get') def test_fetch_not_acceptable(self, mock_get): - mock_get.return_value=NOT_ACCEPTABLE + mock_get.return_value = NOT_ACCEPTABLE with self.assertRaises(BadGateway): ActivityPub.fetch(Object(id='http://orig')) diff --git a/tests/test_common.py b/tests/test_common.py index cd4ba28..3692943 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1,19 +1,11 @@ """Unit tests for common.py.""" -from unittest import mock - from flask import g -from granary import as2 -from oauth_dropins.webutil import appengine_config, util -from oauth_dropins.webutil.testutil import requests_response -import requests # import first so that Fake is defined before URL routes are registered from .testutil import Fake, TestCase import common from flask_app import app -from models import Object, User -import protocol from web import Web diff --git a/tests/test_convert.py b/tests/test_convert.py index 461c769..c6dcff4 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -8,7 +8,6 @@ from granary.tests.test_as1 import ACTOR, COMMENT, DELETE_OF_ID, UPDATE from models import Object from oauth_dropins.webutil.testutil import requests_response from oauth_dropins.webutil.util import parse_mf2 -import requests # import first so that Fake is defined before URL routes are registered from . import testutil @@ -191,7 +190,7 @@ class ConvertTest(testutil.TestCase): resp.headers['Location']) def test_activitypub_to_web_update_no_inner_obj_serve_as_is(self): - # UPDATE's object field is a full object + # Update's object field is a full object Object(id='http://foo', our_as1=UPDATE).put() resp = self.client.get('/convert/web/http://foo', @@ -205,7 +204,7 @@ A ☕ reply """, resp.get_data(as_text=True), ignore_blanks=True) def test_activitypub_to_web_update_inner_obj_too_minimal_serve_as_is(self): - # UPDATE's object field is a full object + # Update's object field is a full object Object(id='http://foo', our_as1=UPDATE).put() Object(id=UPDATE['object']['id'], as2={'id': 'foo'}).put() @@ -259,4 +258,3 @@ A ☕ reply resp = self.client.get(f'/convert/ap/http://nope.com/post', base_url='https://ap.brid.gy/') self.assertEqual(400, resp.status_code) - diff --git a/tests/test_follow.py b/tests/test_follow.py index c3b32e3..4f6d8c2 100644 --- a/tests/test_follow.py +++ b/tests/test_follow.py @@ -14,10 +14,7 @@ from oauth_dropins.webutil.util import json_dumps, json_loads from .testutil import Fake, TestCase from activitypub import ActivityPub -import common -from common import redirect_unwrap -from models import Follower, Object, User -from web import Web +from models import Follower, Object WEBFINGER = requests_response({ 'subject': 'acct:foo@bar', @@ -191,7 +188,7 @@ class FollowTest(TestCase): def check(self, input, resp, expected_follow, mock_get, mock_post): self.assertEqual(302, resp.status_code) - self.assertEqual('/web/alice.com/following',resp.headers['Location']) + self.assertEqual('/web/alice.com/following', resp.headers['Location']) self.assertEqual([f'Followed {input}.'], get_flashed_messages()) diff --git a/tests/test_models.py b/tests/test_models.py index e2ea5aa..a5c2d7c 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,20 +1,15 @@ # coding=utf-8 """Unit tests for models.py.""" -from unittest import mock - from arroba.mst import dag_cbor_cid from Crypto.PublicKey import ECC -from flask import g, get_flashed_messages -from granary import as2 +from flask import g from granary.tests.test_bluesky import ACTOR_PROFILE_BSKY -from multiformats import CID -from oauth_dropins.webutil.testutil import NOW, requests_response +from oauth_dropins.webutil.testutil import NOW # import first so that Fake is defined before URL routes are registered from .testutil import Fake, TestCase -import common -from models import AtpNode, Follower, Object, OBJECT_EXPIRE_AGE, User +from models import AtpNode, Follower, Object, OBJECT_EXPIRE_AGE import protocol from web import Web @@ -158,9 +153,9 @@ class ObjectTest(TestCase): title="Alice"> Alice""", {'actor': { - 'name': 'Alice', - 'icon': {'type': 'Image', 'url': 'http://pic'}, - }}), + 'name': 'Alice', + 'icon': {'type': 'Image', 'url': 'http://pic'}, + }}), ): obj = Object(id='x', as2=as2) self.assert_multiline_in(expected, obj.actor_link()) diff --git a/tests/test_pages.py b/tests/test_pages.py index f4255a1..9945957 100644 --- a/tests/test_pages.py +++ b/tests/test_pages.py @@ -1,32 +1,26 @@ """Unit tests for pages.py.""" -from granary import as2, atom, microformats2, rss -from granary.tests.test_bluesky import REPLY_BSKY +from granary import atom, microformats2, rss from granary.tests.test_as1 import ( ACTOR, COMMENT, - FOLLOW_WITH_ACTOR, - FOLLOW_WITH_OBJECT, - LIKE, NOTE, ) from oauth_dropins.webutil import util -from oauth_dropins.webutil.testutil import requests_response # import first so that Fake is defined before URL routes are registered from .testutil import Fake, TestCase from activitypub import ActivityPub -import common -from models import Object, Follower, User -from web import Web +from models import Object, Follower -from .test_web import ACTOR_AS2, ACTOR_HTML, ACTOR_MF2, REPOST_AS2 +from .test_web import ACTOR_AS2, REPOST_AS2 ACTOR_WITH_PREFERRED_USERNAME = { **ACTOR, 'preferredUsername': 'me', } + def contents(activities): return [(a.get('object') or a)['content'] for a in activities] diff --git a/tests/test_protocol.py b/tests/test_protocol.py index 0353626..e3079c7 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -13,7 +13,6 @@ from app import app from models import Follower, Object, PROTOCOLS, User import protocol from protocol import Protocol -import requests from ui import UIProtocol from web import Web diff --git a/tests/test_redirect.py b/tests/test_redirect.py index 25864ee..3da6a13 100644 --- a/tests/test_redirect.py +++ b/tests/test_redirect.py @@ -5,20 +5,17 @@ from unittest.mock import patch from granary import as2 from oauth_dropins.webutil.testutil import requests_response -import requests # import first so that Fake is defined before URL routes are registered from . import testutil -from common import redirect_unwrap from flask_app import app, cache -from models import Object, User +from models import Object from web import Web from .test_activitypub import ACTOR_BASE_FULL from .test_web import ( ACTOR_AS2, - ACTOR_AS2_FULL, ACTOR_HTML, REPOST_AS2, REPOST_HTML, @@ -146,7 +143,7 @@ class RedirectTest(testutil.TestCase): self._test_as2(as2.CONTENT_TYPE) resp = self.client.get('/r/https://user.com/bar', - headers={'Accept': 'text/html'}) + headers={'Accept': 'text/html'}) self.assertEqual(301, resp.status_code) self.assertEqual('https://user.com/bar', resp.headers['Location']) @@ -162,5 +159,5 @@ class RedirectTest(testutil.TestCase): Object(id='https://user.com/bar', as2={}, deleted=True).put() resp = self.client.get('/r/https://user.com/bar', - headers={'Accept': as2.CONTENT_TYPE}) + headers={'Accept': as2.CONTENT_TYPE}) self.assertEqual(404, resp.status_code, resp.get_data(as_text=True)) diff --git a/tests/test_web.py b/tests/test_web.py index 3c573c4..4e1f3cf 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -4,28 +4,21 @@ import copy from unittest.mock import patch from urllib.parse import urlencode -import feedparser from flask import g, get_flashed_messages -from granary import as1, as2, atom, microformats2 -from httpsig.sign import HeaderSigner -from oauth_dropins.webutil import appengine_config, util -from oauth_dropins.webutil.appengine_config import tasks_client +from granary import as1, as2, microformats2 +from oauth_dropins.webutil import util from oauth_dropins.webutil.appengine_info import APP_ID from oauth_dropins.webutil.testutil import NOW, requests_response from oauth_dropins.webutil.util import json_dumps, json_loads import requests -from requests import HTTPError from werkzeug.exceptions import BadGateway, BadRequest # import first so that Fake is defined before URL routes are registered from . import testutil from activitypub import ActivityPub, postprocess_as2 -from common import ( - CONTENT_TYPE_HTML, - redirect_unwrap, -) -from models import Follower, Object, Target, User +from common import CONTENT_TYPE_HTML +from models import Follower, Object from web import TASKS_LOCATION, Web from . import test_activitypub from .testutil import TestCase @@ -210,7 +203,7 @@ REPLY_HTML = """\ """ REPLY = requests_response(REPLY_HTML, content_type=CONTENT_TYPE_HTML, - url='https://user.com/reply') + url='https://user.com/reply') REPLY_MF2 = util.parse_mf2(REPLY_HTML)['items'][0] REPLY_AS1 = microformats2.json_to_object(REPLY_MF2) CREATE_REPLY_AS1 = { @@ -238,7 +231,7 @@ LIKE = requests_response(LIKE_HTML, content_type=CONTENT_TYPE_HTML, LIKE_MF2 = util.parse_mf2(LIKE_HTML)['items'][0] ACTOR = TestCase.as2_resp({ - 'type' : 'Person', + 'type': 'Person', 'name': 'Mrs. ☕ Foo', 'id': 'https://mas.to/mrs-foo', 'inbox': 'https://mas.to/inbox', @@ -583,9 +576,6 @@ class WebTest(TestCase): self.assertEqual(502, got.status_code) def test_target_fetch_has_no_content_type(self, mock_get, mock_post): - html = REPLY_HTML.replace( - '', - "', - ''), + ''), content_type=CONTENT_TYPE_HTML, url='https://user.com/reply') got = self.client.post('/_ah/queue/webmention', data={ @@ -1148,17 +1138,17 @@ class WebTest(TestCase): self.assert_deliveries(mock_post, ['https://mas.to/inbox'], FOLLOW_FRAGMENT_AS2) - obj = self.assert_object('https://user.com/follow#2', - users=[g.user.key], - source_protocol='web', - status='complete', - mf2=FOLLOW_FRAGMENT_MF2, - as1=FOLLOW_FRAGMENT_AS1, - delivered=['https://mas.to/inbox'], - type='follow', - object_ids=['https://mas.to/mrs-foo'], - labels=['user', 'activity'], - ) + self.assert_object('https://user.com/follow#2', + users=[g.user.key], + source_protocol='web', + status='complete', + mf2=FOLLOW_FRAGMENT_MF2, + as1=FOLLOW_FRAGMENT_AS1, + delivered=['https://mas.to/inbox'], + type='follow', + object_ids=['https://mas.to/mrs-foo'], + labels=['user', 'activity'], + ) followers = Follower.query().fetch() self.assert_equals(1, len(followers)) @@ -1178,7 +1168,7 @@ class WebTest(TestCase): content_type=CONTENT_TYPE_HTML), ACTOR, self.as2_resp({ - 'objectType' : 'Person', + 'objectType': 'Person', 'displayName': 'Mr. ☕ Biff', 'id': 'https://mas.to/mr-biff', 'inbox': 'https://mas.to/inbox/biff', @@ -1277,7 +1267,7 @@ class WebTest(TestCase): type='delete', object_ids=['https://user.com/post'], labels=['user', 'activity'], - ) + ) def test_delete_no_object(self, mock_get, mock_post): mock_get.side_effect = [ @@ -1336,7 +1326,7 @@ class WebTest(TestCase): type='follow', object_ids=['https://mas.to/mrs-foo'], labels=['user', 'activity'], - ) + ) def test_repost_blocklisted_error(self, mock_get, mock_post): """Reposts of non-fediverse (ie blocklisted) sites aren't yet supported.""" @@ -1723,7 +1713,7 @@ class WebProtocolTest(TestCase): self.assert_equals({**REPOST_MF2, 'url': 'https://user.com/repost'}, obj.mf2) def test_fetch_redirect(self, mock_get, __): - mock_get.return_value =requests_response( + mock_get.return_value = requests_response( REPOST_HTML, content_type=CONTENT_TYPE_HTML, redirected_url='http://new/url') obj = Object(id='https://orig/url') @@ -1735,7 +1725,7 @@ class WebProtocolTest(TestCase): def test_fetch_error(self, mock_get, __): mock_get.return_value = requests_response(REPOST_HTML, status=405) - with self.assertRaises(BadGateway) as e: + with self.assertRaises(BadGateway): Web.fetch(Object(id='https://foo'), gateway=True) def test_fetch_run_authorship(self, mock_get, __): diff --git a/tests/test_webfinger.py b/tests/test_webfinger.py index be90933..b0467cb 100644 --- a/tests/test_webfinger.py +++ b/tests/test_webfinger.py @@ -1,7 +1,6 @@ # coding=utf-8 """Unit tests for webfinger.py.""" import copy -import html from unittest.mock import patch import urllib.parse @@ -10,7 +9,6 @@ from oauth_dropins.webutil.testutil import requests_response # import first so that Fake is defined before URL routes are registered from .testutil import Fake, TestCase -import common from web import Web from .test_web import ACTOR_HTML @@ -115,7 +113,7 @@ class HostMetaTest(TestCase): got = self.client.get('/.well-known/host-meta') self.assertEqual(200, got.status_code) self.assertEqual('application/xrd+xml; charset=utf-8', - got.headers['Content-Type']) + got.headers['Content-Type']) body = got.get_data(as_text=True) self.assertTrue(body.startswith('