From 98465907ff4ed4a15ba44fc0b6fa56e74f4c42c7 Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Tue, 10 Oct 2017 07:42:10 -0700 Subject: [PATCH] add new /render endpoint for rendering Responses as HTML --- app.yaml | 4 +++ render.py | 42 +++++++++++++++++++++++++ test/test_render.py | 74 +++++++++++++++++++++++++++++++++++++++++++++ test/testutil.py | 4 ++- 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 render.py create mode 100644 test/test_render.py diff --git a/app.yaml b/app.yaml index 03053f2..f614427 100644 --- a/app.yaml +++ b/app.yaml @@ -74,6 +74,10 @@ handlers: script: salmon.app secure: always +- url: /render + script: render.app + secure: always + - url: /(acct:)?[^/]+/? script: webfinger.app secure: always diff --git a/render.py b/render.py new file mode 100644 index 0000000..5ea081d --- /dev/null +++ b/render.py @@ -0,0 +1,42 @@ +# coding=utf-8 +"""Renders mf2 proxy pages based on stored Responses.""" +import json + +import appengine_config + +from granary import as2, microformats2 +from oauth_dropins.webutil.handlers import ModernHandler +from oauth_dropins.webutil import util +import webapp2 + +from models import Response + + +class RenderHandler(ModernHandler): + """Fetches a stored Response and renders it as HTML.""" + + def get(self): + source = util.get_required_param(self, 'source') + target = util.get_required_param(self, 'target') + + id = '%s %s' % (source, target) + resp = Response.get_by_id(id) + if not resp: + self.abort(404, 'No stored response for %s' % id) + + if resp.source_mf2: + as1 = microformats2.json_to_object(json.loads(resp.source_mf2)) + elif resp.source_as2: + as1 = as2.to_as1(json.loads(resp.source_as2)) + elif resp.source_atom: + self.abort(501, 'Rendering HTML from Atom is not yet implemented.') + else: + self.abort(404, 'Stored response for %s has no data' % id) + + self.response.write(microformats2.activities_to_html([as1])) + + +app = webapp2.WSGIApplication([ + ('/render', RenderHandler), +], debug=appengine_config.DEBUG) + diff --git a/test/test_render.py b/test/test_render.py new file mode 100644 index 0000000..8ce7c8a --- /dev/null +++ b/test/test_render.py @@ -0,0 +1,74 @@ +# coding=utf-8 +"""Unit tests for render.py.""" +from __future__ import unicode_literals +import json + +from models import Response +import testutil +from render import app + + +class RenderTest(testutil.TestCase): + + def setUp(self): + super(RenderTest, self).setUp() + + self.as2 = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'type': 'Note', + 'content': 'A ☕ reply', + 'url': 'http://this/reply', + 'inReplyTo': 'http://orig/post', + } + self.mf2 = { + 'type': ['h-entry'], + 'properties': { + 'url': ['http://this/reply'], + 'content': [{'value': 'A ☕ reply'}], + 'in-reply-to': ['http://orig/post'], + }, + } + self.html = """\ + + + + +
+ + http://this/reply +
+ A ☕ reply +
+ +
+ + +""" + + def test_render_errors(self): + for source, target in ('', ''), ('abc', ''), ('', 'xyz'): + resp = app.get_response('/render?source=%s&target=%s' % (source, target)) + self.assertEquals(400, resp.status_int, resp.body) + + # no Response + resp = app.get_response('/render?source=abc&target=xyz') + self.assertEquals(404, resp.status_int) + + # no source data + Response(id='abc xyz').put() + resp = app.get_response('/render?source=abc&target=xyz') + self.assertEquals(404, resp.status_int) + + def test_render_as2(self): + Response(id='abc xyz', source_as2=json.dumps(self.as2)).put() + resp = app.get_response('/render?source=abc&target=xyz') + self.assertEquals(200, resp.status_int) + self.assert_multiline_equals(self.html, resp.body.decode('utf-8'), + ignore_blanks=True) + + def test_render_mf2(self): + Response(id='abc xyz', source_mf2=json.dumps(self.mf2)).put() + resp = app.get_response('/render?source=abc&target=xyz') + self.assertEquals(200, resp.status_int) + self.assert_multiline_equals(self.html, resp.body.decode('utf-8'), + ignore_blanks=True) diff --git a/test/testutil.py b/test/testutil.py index f5efefc..e828cbf 100644 --- a/test/testutil.py +++ b/test/testutil.py @@ -5,8 +5,10 @@ import unittest from google.appengine.datastore import datastore_stub_util from google.appengine.ext import testbed +from oauth_dropins.webutil import testutil -class TestCase(unittest.TestCase): + +class TestCase(unittest.TestCase, testutil.Asserts): maxDiff = None