def test_activitypub_link_rel_alternate_as2(self, mock_get, mock_post): mock_get.side_effect = [ self.reply, self.orig_html_as2, self.orig_as2, self.actor ] mock_post.return_value = requests_response('abc xyz') got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://a/reply', 'target': 'https://fed.brid.gy/', })) self.assertEquals(200, got.status_int) mock_get.assert_has_calls(( self.req('http://a/reply'), self.req('http://orig/post', headers=CONNEG_HEADERS_AS2_HTML), self.req('http://orig/as2', headers=CONNEG_HEADERS_AS2), self.req('http://orig/author', headers=CONNEG_HEADERS_AS2_HTML), )) args, kwargs = mock_post.call_args self.assertEqual(('https://foo.com/inbox', ), args) self.assertEqual(self.as2_create, kwargs['json'])
def test_activitypub_follow(self, mock_get, mock_post): mock_get.side_effect = [self.follow, self.actor] mock_post.return_value = requests_response('abc xyz') got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://a/follow', 'target': 'https://fed.brid.gy/', })) self.assertEquals(200, got.status_int) mock_get.assert_has_calls(( self.req('http://a/follow'), self.req('http://followee', headers=CONNEG_HEADERS_AS2_HTML), )) args, kwargs = mock_post.call_args self.assertEqual(('https://foo.com/inbox', ), args) self.assertEqual(self.follow_as2, kwargs['json']) headers = kwargs['headers'] self.assertEqual(CONTENT_TYPE_AS2, headers['Content-Type']) rsa_key = kwargs['auth'].header_signer._rsa._key self.assertEqual(self.key.private_pem(), rsa_key.exportKey()) resp = Response.get_by_id('http://a/follow http://followee') self.assertEqual('out', resp.direction) self.assertEqual('activitypub', resp.protocol) self.assertEqual('complete', resp.status) self.assertEqual(self.follow_mf2, json.loads(resp.source_mf2))
def test_activitypub_create_default_url_to_wm_source( self, mock_get, mock_post): """Source post has no u-url. AS2 id should default to webmention source.""" missing_url = requests_response("""\ <html> <body class="h-entry"> <a class="u-repost-of p-name" href="http://orig/post">reposted!</a> <a class="p-author h-card" href="http://orig">Ms. ☕ Baz</a> <a href="http://localhost/"></a> </body> </html> """, content_type=CONTENT_TYPE_HTML) mock_get.side_effect = [missing_url, self.orig_as2, self.actor] mock_post.return_value = requests_response('abc xyz', status=203) got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://a/repost', 'target': 'https://fed.brid.gy/', })) self.assertEquals(203, got.status_int) args, kwargs = mock_post.call_args self.assertEqual(('https://foo.com/inbox', ), args) self.assert_equals(self.repost_as2, kwargs['json'])
def test_activitypub_create_post(self, mock_get, mock_post): mock_get.side_effect = [self.create, self.actor] mock_post.return_value = requests_response('abc xyz') Follower.get_or_create('orig', 'https://mastodon/aaa') Follower.get_or_create('orig', 'https://mastodon/bbb', last_follow=json.dumps({ 'actor': { 'publicInbox': 'https://public/inbox', 'inbox': 'https://unused', } })) Follower.get_or_create('orig', 'https://mastodon/ccc', last_follow=json.dumps({ 'actor': { 'endpoints': { 'sharedInbox': 'https://shared/inbox', }, } })) Follower.get_or_create('orig', 'https://mastodon/ddd', last_follow=json.dumps( {'actor': { 'inbox': 'https://inbox', }})) self.datastore_stub.Flush() got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://orig/post', 'target': 'https://fed.brid.gy/', })) self.assertEquals(200, got.status_int) mock_get.assert_has_calls((self.req('http://orig/post'), )) inboxes = ('https://public/inbox', 'https://shared/inbox', 'https://inbox') for call, inbox in zip(mock_post.call_args_list, inboxes): self.assertEquals((inbox, ), call[0]) self.assertEquals(self.create_as2, call[1]['json']) for inbox in inboxes: resp = Response.get_by_id('http://orig/post %s' % inbox) self.assertEqual('out', resp.direction, inbox) self.assertEqual('activitypub', resp.protocol, inbox) self.assertEqual('complete', resp.status, inbox) self.assertEqual(self.create_mf2, json.loads(resp.source_mf2), inbox)
def test_no_backlink(self, mock_get, mock_post): mock_get.return_value = requests_response( self.reply_html.replace('<a href="http://localhost/"></a>', ''), content_type=CONTENT_TYPE_HTML) got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://a/post', 'target': 'https://fed.brid.gy/', })) self.assertEquals(400, got.status_int) mock_get.assert_has_calls((self.req('http://a/post'), ))
def test_salmon_reply(self, mock_get, mock_post): mock_get.side_effect = [ self.reply, self.orig_html_atom, self.orig_atom ] got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://a/reply', 'target': 'http://orig/post', })) self.assertEquals(200, got.status_int) mock_get.assert_has_calls(( self.req('http://a/reply'), self.req('http://orig/post', headers=CONNEG_HEADERS_AS2_HTML), self.req('http://orig/atom'), )) data = self.verify_salmon(mock_post) parsed = feedparser.parse(data) entry = parsed.entries[0] self.assertEquals('http://a/reply', entry['id']) self.assertIn( { 'rel': 'alternate', 'href': 'http://a/reply', 'type': 'text/html', }, entry['links']) self.assertEquals( { 'type': 'text/html', 'href': 'http://orig/post', 'ref': 'tag:fed.brid.gy,2017-08-22:orig-post' }, entry['thr_in-reply-to']) self.assertEquals( '<div style="white-space: pre;"><a class="u-in-reply-to" href="http://orig/post">foo ☕ bar</a>\n<a href="http://localhost/"></a></div>', entry.content[0]['value']) resp = Response.get_by_id('http://a/reply http://orig/post') self.assertEqual('out', resp.direction) self.assertEqual('ostatus', resp.protocol) self.assertEqual('complete', resp.status) self.assertEqual(self.reply_mf2, json.loads(resp.source_mf2))
def test_activitypub_create_author_only_url(self, mock_get, mock_post): """Mf2 author property is just a URL. We should run full authorship. https://indieweb.org/authorship """ repost = requests_response("""\ <html> <body class="h-entry"> <a class="u-repost-of p-name" href="http://orig/post">reposted!</a> <a class="u-author" href="http://orig"></a> <a href="http://localhost/"></a> </body> </html> """, content_type=CONTENT_TYPE_HTML) author = requests_response("""\ <html> <body class="h-card"> <a class="p-name u-url" rel="me" href="http://orig">Ms. ☕ Baz</a> <img class="u-photo" src="/pic" /> </body> </html> """, content_type=CONTENT_TYPE_HTML) mock_get.side_effect = [repost, author, self.orig_as2, self.actor] mock_post.return_value = requests_response('abc xyz', status=201) got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://a/repost', 'target': 'https://fed.brid.gy/', })) self.assertEquals(201, got.status_int) args, kwargs = mock_post.call_args self.assertEqual(('https://foo.com/inbox', ), args) repost_as2 = copy.deepcopy(self.repost_as2) repost_as2['actor']['image'] = repost_as2['actor']['icon'] = \ {'type': 'Image', 'url': 'http://orig/pic'}, self.assert_equals(repost_as2, kwargs['json'])
def test_activitypub_update_reply(self, mock_get, mock_post): Response(id='http://a/reply http://orig/as2', status='complete').put() mock_get.side_effect = self.activitypub_gets mock_post.return_value = requests_response('abc xyz') got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://a/reply', 'target': 'https://fed.brid.gy/', })) self.assertEquals(200, got.status_int) args, kwargs = mock_post.call_args self.assertEqual(('https://foo.com/inbox', ), args) self.assertEqual(self.as2_update, kwargs['json'])
def test_salmon_no_target_atom(self, mock_get, mock_post): orig_no_atom = requests_response( """\ <html> <body>foo</body> </html>""", 'http://orig/url') mock_get.side_effect = [self.reply, orig_no_atom] got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://a/reply', 'target': 'http://orig/post', })) self.assertEquals(400, got.status_int) self.assertIn('Target post http://orig/url has no Atom link', got.body) self.assertIsNone( Response.get_by_id('http://a/reply http://orig/post'))
def test_no_targets(self, mock_get, mock_post): mock_get.return_value = requests_response( """ <html> <body class="h-entry"> <p class="e-content">no one to send to! <a href="http://localhost/"></a></p> </body> </html>""", content_type=CONTENT_TYPE_HTML) got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://a/post', 'target': 'https://fed.brid.gy/', })) self.assertEquals(200, got.status_int) mock_get.assert_has_calls((self.req('http://a/post'), ))
def test_no_source_entry(self, mock_get, mock_post): mock_get.return_value = requests_response( """ <html> <body> <p>nothing to see here except <a href="http://localhost/">link</a></p> </body> </html>""", content_type=CONTENT_TYPE_HTML) got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://a/post', 'target': 'https://fed.brid.gy/', })) self.assertEquals(400, got.status_int) mock_get.assert_has_calls((self.req('http://a/post'), ))
def test_salmon_get_salmon_from_webfinger(self, mock_get, mock_post): orig_atom = requests_response("""\ <?xml version="1.0"?> <entry xmlns="http://www.w3.org/2005/Atom"> <author> <name>ryan</name> <email>ryan@orig</email> </author> <id>tag:fed.brid.gy,2017-08-22:orig-post</id> </entry> """) webfinger = requests_response({ 'subject': 'acct:ryan@orig', 'links': [{ 'rel': 'salmon', 'href': 'http://orig/@ryan/salmon', }], }) mock_get.side_effect = [ self.reply, self.orig_html_atom, orig_atom, webfinger ] got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://a/reply', 'target': 'http://orig/post', })) self.assertEquals(200, got.status_int) mock_get.assert_any_call( 'http://orig/.well-known/webfinger?resource=acct:ryan@orig', headers=HEADERS, timeout=util.HTTP_TIMEOUT, verify=False) self.assertEqual(('http://orig/@ryan/salmon', ), mock_post.call_args[0])
def test_activitypub_create_with_image(self, mock_get, mock_post): create_html = self.create_html.replace( '</body>', '<img class="u-photo" src="http://im/age" />\n</body>') mock_get.side_effect = [ requests_response(create_html, content_type=CONTENT_TYPE_HTML), self.actor, ] mock_post.return_value = requests_response('abc xyz ') Follower.get_or_create('orig', 'https://mastodon/aaa', last_follow=json.dumps( {'actor': { 'inbox': 'https://inbox' }})) self.datastore_stub.Flush() got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://orig/post', 'target': 'https://fed.brid.gy/', })) self.assertEquals(200, got.status_int) self.assertEquals(('https://inbox', ), mock_post.call_args[0]) create = copy.deepcopy(self.create_as2) create['object'].update({ 'image': [{ 'url': 'http://im/age', 'type': 'Image' }], 'attachment': [{ 'url': 'http://im/age', 'type': 'Image' }], }) self.assertEquals(create, mock_post.call_args[1]['json'])
def test_salmon_like(self, mock_get, mock_post): mock_get.side_effect = [self.like, self.orig_html_atom, self.orig_atom] got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://a/like', 'target': 'http://orig/post', })) self.assertEquals(200, got.status_int) mock_get.assert_has_calls(( self.req('http://a/like'), self.req('http://orig/post', headers=CONNEG_HEADERS_AS2_HTML), self.req('http://orig/atom'), )) data = self.verify_salmon(mock_post) parsed = feedparser.parse(data) entry = parsed.entries[0] self.assertEquals('http://a/like', entry['id']) self.assertIn( { 'rel': 'alternate', 'href': 'http://a/like', 'type': 'text/html', }, entry['links']) self.assertEquals('http://orig/post', entry['activity_object']) resp = Response.get_by_id('http://a/like http://orig/post') self.assertEqual('out', resp.direction) self.assertEqual('ostatus', resp.protocol) self.assertEqual('complete', resp.status) self.assertEqual(self.like_mf2, json.loads(resp.source_mf2))
def test_activitypub_create_reply_attributed_to_id_only( self, mock_get, mock_post): """Based on PeerTube's AS2. https://github.com/snarfed/bridgy-fed/issues/40 """ del self.orig_as2_data['actor'] self.orig_as2_data['attributedTo'] = [{ 'type': 'Person', 'id': 'http://orig/author', }] orig_as2_resp = requests_response(self.orig_as2_data, content_type=CONTENT_TYPE_AS2 + '; charset=utf-8') mock_get.side_effect = [self.reply, orig_as2_resp, self.actor] mock_post.return_value = requests_response('abc xyz', status=203) got = app.get_response('/webmention', method='POST', body=urllib.urlencode({ 'source': 'http://a/reply', 'target': 'https://fed.brid.gy/', })) self.assertEquals(203, got.status_int) mock_get.assert_has_calls(( self.req('http://a/reply'), self.req('http://orig/post', headers=CONNEG_HEADERS_AS2_HTML), self.req('http://orig/author', headers=CONNEG_HEADERS_AS2_HTML), )) args, kwargs = mock_post.call_args self.assertEqual(('https://foo.com/inbox', ), args) self.assertEqual(self.as2_create, kwargs['json'])