Merge pull request #278 from michael-lazar/remove_tornado

Remove tornado
This commit is contained in:
Michael Lazar
2016-08-10 02:16:34 -07:00
committed by GitHub
18 changed files with 1323 additions and 147 deletions

View File

@@ -5,8 +5,7 @@ python:
- "3.4" - "3.4"
- "3.5" - "3.5"
before_install: before_install:
- pip install coveralls pytest coverage mock pylint - pip install coveralls pytest coverage mock pylint vcrpy
- pip install git+https://github.com/kevin1024/vcrpy.git
install: install:
- pip install . - pip install .
script: script:

View File

@@ -9,7 +9,6 @@ import warnings
import six import six
import praw import praw
import tornado
import requests import requests
from . import docs from . import docs
@@ -158,7 +157,5 @@ def main():
# Ensure sockets are closed to prevent a ResourceWarning # Ensure sockets are closed to prevent a ResourceWarning
if 'reddit' in locals(): if 'reddit' in locals():
reddit.handler.http.close() reddit.handler.http.close()
# Explicitly close file descriptors opened by Tornado's IOLoop
tornado.ioloop.IOLoop.current().close(all_fds=True)
sys.exit(main()) sys.exit(main())

View File

@@ -15,9 +15,9 @@ from .objects import KeyMap
PACKAGE = os.path.dirname(__file__) PACKAGE = os.path.dirname(__file__)
HOME = os.path.expanduser('~') HOME = os.path.expanduser('~')
TEMPLATE = os.path.join(PACKAGE, 'templates') TEMPLATES = os.path.join(PACKAGE, 'templates')
DEFAULT_CONFIG = os.path.join(TEMPLATE, 'rtv.cfg') DEFAULT_CONFIG = os.path.join(TEMPLATES, 'rtv.cfg')
DEFAULT_MAILCAP = os.path.join(TEMPLATE, 'mailcap') DEFAULT_MAILCAP = os.path.join(TEMPLATES, 'mailcap')
XDG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.join(HOME, '.config')) XDG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.join(HOME, '.config'))
CONFIG = os.path.join(XDG_HOME, 'rtv', 'rtv.cfg') CONFIG = os.path.join(XDG_HOME, 'rtv', 'rtv.cfg')
MAILCAP = os.path.join(HOME, '.mailcap') MAILCAP = os.path.join(HOME, '.mailcap')

View File

@@ -103,3 +103,27 @@ SUBMISSION_EDIT_FILE = """{content}
# #
# Editing {name} # Editing {name}
""" """
OAUTH_ACCESS_DENIED = """\
<h1 style="color: red">Access Denied</h1><hr>
<p><span style="font-weight: bold">Reddit Terminal Viewer</span> was
denied access and will continue to operate in unauthenticated mode,
you can close this window.</p>
"""
OAUTH_ERROR = """\
<h1 style="color: red">Error</h1><hr>
<p>{error}</p>
"""
OAUTH_INVALID = """\
<h1>Wait...</h1><hr>
<p>This page is supposed to be a Reddit OAuth callback.
You can't just come here hands in your pocket!</p>
"""
OAUTH_SUCCESS = """\
<h1 style="color: green">Access Granted</h1><hr>
<p><span style="font-weight: bold">Reddit Terminal Viewer</span>
will now log in, you can close this window.</p>
"""

View File

@@ -1,62 +1,118 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import os
import time import time
import uuid import uuid
import string
import codecs
import logging
import threading
from concurrent.futures import ThreadPoolExecutor #pylint: disable=import-error
from tornado import gen, ioloop, web, httpserver from six.moves.urllib.parse import urlparse, parse_qs
from six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from .config import TEMPLATE from . import docs
from .config import TEMPLATES
class OAuthHandler(web.RequestHandler): _logger = logging.getLogger(__name__)
"""
Intercepts the redirect that Reddit sends the user to after they verify or
deny the application access.
The GET should supply 3 request params: INDEX = os.path.join(TEMPLATES, 'index.html')
state: Unique id that was supplied by us at the beginning of the
process to verify that the session matches.
code: Code that we can use to generate the refresh token.
error: If an error occurred, it will be placed here.
"""
def initialize(self, display=None, params=None):
self.display = display
self.params = params
def get(self): class OAuthHandler(BaseHTTPRequestHandler):
self.params['state'] = self.get_argument('state', default=None)
self.params['code'] = self.get_argument('code', default=None)
self.params['error'] = self.get_argument('error', default=None)
self.render('index.html', **self.params) # params are stored as a global because we don't have control over what
# gets passed into the handler __init__. These will be accessed by the
# OAuthHelper class.
params = {'state': None, 'code': None, 'error': None}
shutdown_on_request = True
complete = self.params['state'] and self.params['code'] def do_GET(self):
if complete or self.params['error']: """
# Stop IOLoop if using a background browser such as firefox Accepts GET requests to http://localhost:6500/, and stores the query
if self.display: params in the global dict. If shutdown_on_request is true, stop the
ioloop.IOLoop.current().stop() server after the first successful request.
The http request may contain the following query params:
- state : unique identifier, should match what we passed to reddit
- code : code that can be exchanged for a refresh token
- error : if provided, the OAuth error that occurred
"""
parsed_path = urlparse(self.path)
if parsed_path.path != '/':
self.send_error(404)
qs = parse_qs(parsed_path.query)
self.params['state'] = qs['state'][0] if 'state' in qs else None
self.params['code'] = qs['code'][0] if 'code' in qs else None
self.params['error'] = qs['error'][0] if 'error' in qs else None
body = self.build_body()
# send_response also sets the Server and Date headers
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=UTF-8')
self.send_header('Content-Length', len(body))
self.end_headers()
self.wfile.write(body)
if self.shutdown_on_request:
# Shutdown the server after serving the request
# http://stackoverflow.com/a/22533929
thread = threading.Thread(target=self.server.shutdown)
thread.daemon = True
thread.start()
def log_message(self, format, *args):
"""
Redirect logging to our own handler instead of stdout
"""
_logger.debug(format, *args)
def build_body(self, template_file=INDEX):
"""
Params:
template_file (text): Path to an index.html template
Returns:
body (bytes): THe utf-8 encoded document body
"""
if self.params['error'] == 'access_denied':
message = docs.OAUTH_ACCESS_DENIED
elif self.params['error'] is not None:
message = docs.OAUTH_ERROR.format(error=self.params['error'])
elif self.params['state'] is None or self.params['code'] is None:
message = docs.OAUTH_INVALID
else:
message = docs.OAUTH_SUCCESS
with codecs.open(template_file, 'r', 'utf-8') as fp:
index_text = fp.read()
body = string.Template(index_text).substitute(message=message)
body = codecs.encode(body, 'utf-8')
return body
class OAuthHelper(object): class OAuthHelper(object):
params = OAuthHandler.params
def __init__(self, reddit, term, config): def __init__(self, reddit, term, config):
self.term = term self.term = term
self.reddit = reddit self.reddit = reddit
self.config = config self.config = config
self.http_server = None # Wait to initialize the server, we don't want to reserve the port
self.params = {'state': None, 'code': None, 'error': None} # unless we know that the server needs to be used.
self.server = None
# Initialize Tornado webapp
# Pass a mutable params object so the request handler can modify it
kwargs = {'display': self.term.display, 'params': self.params}
routes = [('/', OAuthHandler, kwargs)]
self.callback_app = web.Application(
routes, template_path=TEMPLATE)
self.reddit.set_oauth_app_info( self.reddit.set_oauth_app_info(
self.config['oauth_client_id'], self.config['oauth_client_id'],
@@ -79,40 +135,53 @@ class OAuthHelper(object):
self.config.refresh_token) self.config.refresh_token)
return return
# https://github.com/tornadoweb/tornado/issues/1420
io = ioloop.IOLoop.current()
# Start the authorization callback server
if self.http_server is None:
self.http_server = httpserver.HTTPServer(self.callback_app)
self.http_server.listen(self.config['oauth_redirect_port'])
state = uuid.uuid4().hex state = uuid.uuid4().hex
authorize_url = self.reddit.get_authorize_url( authorize_url = self.reddit.get_authorize_url(
state, scope=self.config['oauth_scope'], refreshable=True) state, scope=self.config['oauth_scope'], refreshable=True)
if self.server is None:
address = ('', self.config['oauth_redirect_port'])
self.server = HTTPServer(address, OAuthHandler)
if self.term.display: if self.term.display:
# Open a background browser (e.g. firefox) which is non-blocking. # Open a background browser (e.g. firefox) which is non-blocking.
# Stop the iloop when the user hits the auth callback, at which # The server will block until it responds to its first request,
# point we continue and check the callback params. # at which point we can check the callback params.
OAuthHandler.shutdown_on_request = True
with self.term.loader('Opening browser for authorization'): with self.term.loader('Opening browser for authorization'):
self.term.open_browser(authorize_url) self.term.open_browser(authorize_url)
io.start() self.server.serve_forever()
if self.term.loader.exception: if self.term.loader.exception:
# Don't need to call server.shutdown() because serve_forever()
# is wrapped in a try-finally that doees it for us.
return return
else: else:
# Open the terminal webbrowser in a background thread and wait # Open the terminal webbrowser in a background thread and wait
# while for the user to close the process. Once the process is # while for the user to close the process. Once the process is
# closed, the iloop is stopped and we can check if the user has # closed, the iloop is stopped and we can check if the user has
# hit the callback URL. # hit the callback URL.
OAuthHandler.shutdown_on_request = False
with self.term.loader('Redirecting to reddit', delay=0): with self.term.loader('Redirecting to reddit', delay=0):
# This load message exists to provide user feedback # This load message exists to provide user feedback
time.sleep(1) time.sleep(1)
io.add_callback(self._async_open_browser, authorize_url)
io.start() thread = threading.Thread(target=self.server.serve_forever)
thread.daemon = True
thread.start()
try:
self.term.open_browser(authorize_url)
except Exception as e:
# If an exception is raised it will be seen by the thread
# so we don't need to explicitly shutdown() the server
_logger.exception(e)
self.term.show_notification('Browser Error')
else:
self.server.shutdown()
finally:
thread.join()
if self.params['error'] == 'access_denied': if self.params['error'] == 'access_denied':
self.term.show_notification('Declined access') self.term.show_notification('Denied access')
return return
elif self.params['error']: elif self.params['error']:
self.term.show_notification('Authentication error') self.term.show_notification('Authentication error')
@@ -138,10 +207,4 @@ class OAuthHelper(object):
def clear_oauth_data(self): def clear_oauth_data(self):
self.reddit.clear_authentication() self.reddit.clear_authentication()
self.config.delete_refresh_token() self.config.delete_refresh_token()
@gen.coroutine
def _async_open_browser(self, url):
with ThreadPoolExecutor(max_workers=1) as executor:
yield executor.submit(self.term.open_browser, url)
ioloop.IOLoop.current().stop()

View File

@@ -25,20 +25,7 @@
</style> </style>
</head> </head>
<body> <body>
{% if error == 'access_denied' %} ${message}
<h1 style="color: red">Access Denied</h1><hr>
<p><span style="font-weight: bold">Reddit Terminal Viewer</span> was denied access and will continue to operate in unauthenticated mode, you can close this window.
{% elif error is not None %}
<h1 style="color: red">Error</h1><hr>
<p>{{ error }}</p>
{% elif (state is None or code is None) %}
<h1>Wait...</h1><hr>
<p>This page is supposed to be a Reddit OAuth callback. You can't just come here hands in your pocket!</p>
{% else %}
<h1 style="color: green">Access Granted</h1><hr>
<p><span style="font-weight: bold">Reddit Terminal Viewer</span> will now log in, you can close this window.</p>
{% end %}
<div id="footer">View the <a href="http://www.github.com/michael-lazar/rtv">Documentation</a></div> <div id="footer">View the <a href="http://www.github.com/michael-lazar/rtv">Documentation</a></div>
</body> </body>
</html> </html>

View File

@@ -728,7 +728,7 @@ class Terminal(object):
# space, assume that a newline operation was intended by the user # space, assume that a newline operation was intended by the user
stack, current_line = [], '' stack, current_line = [], ''
for line in text.split('\n'): for line in text.split('\n'):
if line.endswith(' '): if line.endswith(' ') or not line:
stack.append(current_line + line.rstrip()) stack.append(current_line + line.rstrip())
current_line = '' current_line = ''
else: else:

View File

@@ -3,12 +3,9 @@ universal = 1
[metadata] [metadata]
requires-dist = requires-dist =
tornado
praw>=3.5,<4 praw>=3.5,<4
six six
requests requests
kitchen kitchen
beautifulsoup4 beautifulsoup4
mailcap-fix mailcap-fix
futures; python_version=="2.6" or python_version=="2.7"

View File

@@ -3,12 +3,8 @@ import setuptools
from version import __version__ as version from version import __version__ as version
requirements = ['tornado', 'praw==3.5.0', 'six', 'requests', 'kitchen', requirements = ['praw==3.5.0', 'six', 'requests', 'kitchen', 'beautifulsoup4',
'beautifulsoup4', 'mailcap-fix'] 'mailcap-fix']
# Python 2: add required concurrent.futures backport from Python 3.2
if sys.version_info.major <= 2:
requirements.append('futures')
setuptools.setup( setuptools.setup(
name='rtv', name='rtv',
@@ -23,8 +19,6 @@ setuptools.setup(
packages=['rtv'], packages=['rtv'],
package_data={'rtv': ['templates/*']}, package_data={'rtv': ['templates/*']},
data_files=[("share/man/man1", ["rtv.1"])], data_files=[("share/man/man1", ["rtv.1"])],
extras_require={
':python_version=="2.6" or python_version=="2.7"': ['futures']},
install_requires=requirements, install_requires=requirements,
entry_points={'console_scripts': ['rtv=rtv.__main__:main']}, entry_points={'console_scripts': ['rtv=rtv.__main__:main']},
classifiers=[ classifiers=[

View File

@@ -0,0 +1,963 @@
interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Connection: [keep-alive]
User-Agent: [rtv test suite PRAW/3.5.0 Python/3.4.0 b'Linux-3.13.0-92-generic-x86_64-with-Ubuntu-14.04-trusty']
method: GET
uri: https://api.reddit.com/r/python/.json?limit=1024
response:
body:
string: !!binary |
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
headers:
CF-RAY: [2d01eb8059cb282e-SJC]
Connection: [keep-alive]
Content-Encoding: [gzip]
Content-Length: ['45307']
Content-Type: [application/json; charset=UTF-8]
Date: ['Wed, 10 Aug 2016 08:01:28 GMT']
Server: [cloudflare-nginx]
Set-Cookie: ['__cfduid=d8b53ff1c55120830a38650f8114cbcc71470816087; expires=Thu,
10-Aug-17 08:01:27 GMT; path=/; domain=.reddit.com; HttpOnly']
Strict-Transport-Security: [max-age=15552000; includeSubDomains; preload]
Vary: [accept-encoding]
X-Moose: [majestic]
access-control-allow-origin: ['*']
access-control-expose-headers: ['X-Reddit-Tracking, X-Moose']
cache-control: ['max-age=0, must-revalidate']
x-content-type-options: [nosniff]
x-frame-options: [SAMEORIGIN]
x-reddit-tracking: ['https://pixel.redditmedia.com/pixel/of_destiny.png?v=g10byB%2Bscoc5lg0h0tH9suQqTZOnh80KOioCatbDVnM5WVDlaijt4zGOq0Qpa8UnGV4YE14t1fg%3D']
x-ua-compatible: [IE=edge]
x-xss-protection: [1; mode=block]
status: {code: 200, message: OK}
- request:
body: redirect_uri=http%3A%2F%2F127.0.0.1%3A65000%2F&refresh_token=**********&grant_type=refresh_token
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: ['**********']
Connection: [keep-alive]
Content-Length: ['122']
Content-Type: [application/x-www-form-urlencoded]
Cookie: [__cfduid=d8b53ff1c55120830a38650f8114cbcc71470816087]
User-Agent: [rtv test suite PRAW/3.5.0 Python/3.4.0 b'Linux-3.13.0-92-generic-x86_64-with-Ubuntu-14.04-trusty']
method: POST
uri: https://api.reddit.com/api/v1/access_token/
response:
body:
string: !!binary |
H4sIAAAAAAAAAyWMuwrCQBBFf2XY2kIjWtgpKax8oH3Y7F50IsnGmTEaxH+X1erC4Zz7dj4EqFaW
bujcily5vR9Oi/CMZbe+bU7HotmXs6Y4z3xyE3I/r7KxR5ZreIFkHjFwQMUx413qkCFePQu04vw8
X06nE3Ia0r9FZKMrqyUZiSM6YxupHfVRC2JkU+qFB29ooeovUBL4SII+iZH6AaSPumXLo0G4Bg3J
4D5f4B5kRtYAAAA=
headers:
CF-RAY: [2d01eb8b8a14282e-SJC]
Connection: [keep-alive]
Content-Encoding: [gzip]
Content-Type: [application/json; charset=UTF-8]
Date: ['Wed, 10 Aug 2016 08:01:29 GMT']
Server: [cloudflare-nginx]
Strict-Transport-Security: [max-age=15552000; includeSubDomains; preload]
X-Moose: [majestic]
cache-control: ['max-age=0, must-revalidate']
x-content-type-options: [nosniff]
x-frame-options: [SAMEORIGIN]
x-xss-protection: [1; mode=block]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: ['**********']
Connection: [keep-alive]
User-Agent: [rtv test suite PRAW/3.5.0 Python/3.4.0 b'Linux-3.13.0-92-generic-x86_64-with-Ubuntu-14.04-trusty']
method: GET
uri: https://oauth.reddit.com/api/v1/me.json
response:
body:
string: !!binary |
H4sIAFvfqlcC/31V23LbQAj9lY6fO53EcZukP8OgFZIZrXbVvdhxMvn3wlqKVnXSNxtYOBwO6G3H
EWicrL8Q7X5/69BG+v5td8QII7KtTA5HjdgZPrHlV0zsHUxHfCV42InfBMJErYTcH/Y/9w/3vw53
P+40F7cEXfAjBN/4FKucUjzmOJFry8PFPKeCnMyS7m7/tKRTaL79F55lN8CAYUQx7vfPmt1BQ0n/
fyT240gufcQ9HsTYSbEcSHG9yRtJRMAj9gRToBPTOYL1fS94fE4SlELWXIF6jomC0CdIoLP+DImi
RrztThgYnf6Wmi4Fb2GvJNHLRIELBtaO9w/vBfuJ4IiTEMGuB+fPaxlsI4WTlAk0+ZDEv/r+C5bd
GkhjQ629gMHQLoFf4bz/DOfhueCkHs0FImEwR83TrSVG37AlOFMDCUNPW6TSBWA2qpna+EphgNZL
xwlSQDOszpjYDIL4OjEdzuzIUx9QFGW8H7hMbVMjeWGqy05Rzw4f0PUUSP2qdhlX9YzHPgfouZOM
ToiOG4xCBDpBSKctCsozAJi8ZXNZPZ0PRqaZ0lSFz+w42RoZdYPOUVi9kx9o9K73CiFRmcdmNjE3
Ar/l9OlsHnU2n7Moim28kAFGQA7FuVVQkU6W/Rf2VrOj8ywj2SGD5njbZjQiFnvDS+lbJtByIFNt
i4QTOdXdzZMJcyTY1O/LQsQUQWa9CVZk102AllEw1hzj3P+mxQ/uIGRb6yUJh8LP7ZQiJVVv5bhI
XG7EYwJO9eRQJH3ipDsRTixzPwdOeiYX/6JJepG5VpqcfExQ1rIKlsCr3mAUAhFW6DOkOnFjvRmW
i/MVHDmkSwEViddDcv+0PpAD3Htb314u17U26J+d/ePQq/o0XLqZOJQvgPhctlbjXONfRMC5SHa5
1FKQO5ZzVNauSlvymGt7yrQ+uH4LdAGrCvNXQKu8/wUqtT8UrwYAAA==
headers:
CF-RAY: [2d01eb99c3b50651-SJC]
Connection: [keep-alive]
Content-Encoding: [gzip]
Content-Length: ['724']
Content-Type: [application/json; charset=UTF-8]
Date: ['Wed, 10 Aug 2016 08:01:31 GMT']
Server: [cloudflare-nginx]
Set-Cookie: ['__cfduid=d2eed5c921f4a1e4bd4cb46e8350605a71470816091; expires=Thu,
10-Aug-17 08:01:31 GMT; path=/; domain=.reddit.com; HttpOnly']
Strict-Transport-Security: [max-age=15552000; includeSubDomains; preload]
Vary: [accept-encoding]
X-Moose: [majestic]
cache-control: ['private, s-maxage=0, max-age=0, must-revalidate', 'max-age=0,
must-revalidate']
expires: ['-1']
x-content-type-options: [nosniff]
x-frame-options: [SAMEORIGIN]
x-ratelimit-remaining: ['598.0']
x-ratelimit-reset: ['509']
x-ratelimit-used: ['2']
x-xss-protection: [1; mode=block]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: ['**********']
Connection: [keep-alive]
Cookie: [__cfduid=d2eed5c921f4a1e4bd4cb46e8350605a71470816091]
User-Agent: [rtv test suite PRAW/3.5.0 Python/3.4.0 b'Linux-3.13.0-92-generic-x86_64-with-Ubuntu-14.04-trusty']
method: GET
uri: https://oauth.reddit.com/api/multi/mine/.json
response:
body:
string: !!binary |
H4sIAF3fqlcC/3VUbUsbQRD+K9crSAvGKNEilX5IlFCotkIC/WDk2NudZIfsG/sSOcX/3tm8qHdp
vhx7zzMzz8zszjy8lEs0ovxelLesBgXiLqmI5XFRChYZ4S8lZ6YCgZF+ok+QKQxOsaYyTEN29SCI
dxBDdvw/KiBwjy6iNZWMWmWLIxWvPvV6xeS6+jMeF73e0SJeZVDgquCKhfBjVmoxK3e4y4d7aaMN
hZ0XG4UiS5wUR0y7K7IZZMP+2nJmZubNjVuHIIq5t7rIICukhzkJ9FMA39/E6uvtIcfc6B6k1zps
l9pOcX2m/Hf4rr7f2/JyL7gHFiF3/Wzw7fLibDC4OD85zcQ6xyrnmPtzUDoHQU6dTD430iSlCAmp
3pgEwh5e3i5C24W2RrCmfD0u3uEnjwsFzcIaeEIl2uQkglIsSphE5tsUvQdjrVBQpxjb1LVEpZq/
+dMm7pEvFVxLgAA31qSO3zB5a4ZG/MQwgj3npGtvQyc/G8h+KmGKpmM+VMhhQ96kuoZO+ioZ5pJz
Ha9gRUNwG/TMCKtJHKFN5NgMVTOiD3QiTW3dQXhynC2Bs5j83jUE6ySCs5Hp1CnyxvqO8USCCMNb
G6m+33ZK6m1+aU6jRLMIv5jXrM2NrKZmUOYjei7CLrql6gCBLnyvCXXyHqPN74QnIbq3wyXdrYOO
mGPRE57j0XuVzC/bfJOW6JB3CnZgTEM9d7ZrP2YmsrHHvfZNsu0dGtorFOzxfbqqFHm5mbDzy/OL
7YQtoam4VdZn388cYDC/zNO0woA1KozNOg+PK4rxNmY7sQw8AS5kpB5XgUvY9iDvKuSZpsJlhjaz
y3FFQZ/Zeu05yZ6hGuxN8sfVqNe7+NCKy+stL7WPu+zh4JZ4/HKQ+lq+vj7+A1SYhRP9BQAA
headers:
CF-RAY: [2d01eba4a40a0651-SJC]
Connection: [keep-alive]
Content-Encoding: [gzip]
Content-Length: ['681']
Content-Type: [application/json; charset=UTF-8]
Date: ['Wed, 10 Aug 2016 08:01:33 GMT']
Server: [cloudflare-nginx]
Strict-Transport-Security: [max-age=15552000; includeSubDomains; preload]
Vary: [accept-encoding]
X-Moose: [majestic]
cache-control: ['private, s-maxage=0, max-age=0, must-revalidate', 'max-age=0,
must-revalidate']
expires: ['-1']
x-content-type-options: [nosniff]
x-frame-options: [SAMEORIGIN]
x-ratelimit-remaining: ['597.0']
x-ratelimit-reset: ['508']
x-ratelimit-used: ['3']
x-reddit-tracking: ['https://pixel.redditmedia.com/pixel/of_destiny.png?v=8vk4hb0j64upFrGIvLcAUI0QdaP%2BF2nE7aCdt4yoQYWLsaxTRrj4KRlbnOCUYnkjvOM604a5yR5dO6vf6XcEAAZ2BNQ%2BDxMd']
x-ua-compatible: [IE=edge]
x-xss-protection: [1; mode=block]
status: {code: 200, message: OK}
version: 1

View File

@@ -4,14 +4,16 @@ from __future__ import unicode_literals
import os import os
import curses import curses
import logging import logging
import threading
from functools import partial from functools import partial
import praw import praw
import pytest import pytest
from vcr import VCR from vcr import VCR
from six.moves.urllib.parse import urlparse, parse_qs from six.moves.urllib.parse import urlparse, parse_qs
from six.moves.BaseHTTPServer import HTTPServer
from rtv.oauth import OAuthHelper from rtv.oauth import OAuthHelper, OAuthHandler
from rtv.config import Config from rtv.config import Config
from rtv.terminal import Terminal from rtv.terminal import Terminal
from rtv.subreddit import SubredditPage from rtv.subreddit import SubredditPage
@@ -196,6 +198,21 @@ def oauth(reddit, terminal, config):
return OAuthHelper(reddit, terminal, config) return OAuthHelper(reddit, terminal, config)
@pytest.yield_fixture()
def oauth_server():
# Start the OAuth server on a random port in the background
server = HTTPServer(('', 0), OAuthHandler)
server.url = 'http://{0}:{1}/'.format(*server.server_address)
thread = threading.Thread(target=server.serve_forever)
thread.start()
try:
yield server
finally:
server.shutdown()
thread.join()
server.server_close()
@pytest.fixture() @pytest.fixture()
def submission_page(reddit, terminal, config, oauth): def submission_page(reddit, terminal, config, oauth):
submission = 'https://www.reddit.com/r/Python/comments/2xmo63' submission = 'https://www.reddit.com/r/Python/comments/2xmo63'

View File

@@ -25,6 +25,25 @@ def test_copy_default_config():
assert permissions == 0o664 assert permissions == 0o664
def test_copy_default_config_cancel():
"Pressing ``n`` should cancel the copy"
with NamedTemporaryFile(suffix='.cfg') as fp:
with mock.patch('rtv.config.six.moves.input', return_value='n'):
copy_default_config(fp.name)
assert not fp.read()
def test_copy_config_interrupt():
"Pressing ``Ctrl-C`` should cancel the copy"
with NamedTemporaryFile(suffix='.cfg') as fp:
with mock.patch('rtv.config.six.moves.input') as func:
func.side_effect = KeyboardInterrupt
copy_default_config(fp.name)
assert not fp.read()
def test_copy_default_mailcap(): def test_copy_default_mailcap():
"Make sure the example mailcap file was included in the package" "Make sure the example mailcap file was included in the package"
@@ -180,6 +199,11 @@ def test_config_refresh_token():
def test_config_history(): def test_config_history():
"Ensure that the history can be loaded and saved" "Ensure that the history can be loaded and saved"
# Should still be able to load if the file doesn't exist
config = Config(history_file='/fake_path/fake_file')
config.load_history()
assert len(config.history) == 0
with NamedTemporaryFile(delete=False) as fp: with NamedTemporaryFile(delete=False) as fp:
config = Config(history_file=fp.name, history_size=3) config = Config(history_file=fp.name, history_size=3)

View File

@@ -6,13 +6,16 @@ from itertools import islice
import six import six
import praw import praw
import mock
import pytest import pytest
from rtv.content import ( from rtv.content import (
Content, SubmissionContent, SubredditContent, SubscriptionContent) Content, SubmissionContent, SubredditContent, SubscriptionContent)
from rtv import exceptions from rtv import exceptions
try:
from unittest import mock
except ImportError:
import mock
# Test entering a bunch of text into the prompt # Test entering a bunch of text into the prompt
# (text, parsed subreddit, parsed order) # (text, parsed subreddit, parsed order)

View File

@@ -1,12 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from tornado.web import Application import requests
from tornado.testing import AsyncHTTPTestCase
from praw.errors import OAuthException from praw.errors import OAuthException
from rtv.oauth import OAuthHelper, OAuthHandler from rtv.oauth import OAuthHelper, OAuthHandler
from rtv.config import TEMPLATE
try: try:
from unittest import mock from unittest import mock
@@ -14,38 +13,48 @@ except ImportError:
import mock import mock
class TestAuthHandler(AsyncHTTPTestCase): def test_oauth_handler_not_found(oauth_server):
def get_app(self): url = oauth_server.url + 'favicon.ico'
self.params = {} resp = requests.get(url)
handler = [('/', OAuthHandler, {'params': self.params})] assert resp.status_code == 404
return Application(handler, template_path=TEMPLATE)
def test_no_callback(self):
resp = self.fetch('/')
assert resp.code == 200
assert self.params['error'] is None
assert 'Wait...' in resp.body.decode()
def test_access_denied(self): def test_oauth_handler_no_callback(oauth_server):
resp = self.fetch('/?error=access_denied')
assert resp.code == 200
assert self.params['error'] == 'access_denied'
assert 'was denied access' in resp.body.decode()
def test_error(self): resp = requests.get(oauth_server.url)
resp = self.fetch('/?error=fake') assert resp.status_code == 200
assert resp.code == 200 assert 'Wait...' in resp.text
assert self.params['error'] == 'fake' assert OAuthHandler.params['error'] is None
assert 'fake' in resp.body.decode()
def test_success(self):
resp = self.fetch('/?state=fake_state&code=fake_code') def test_oauth_handler_access_denied(oauth_server):
assert resp.code == 200
assert self.params['error'] is None url = oauth_server.url + '?error=access_denied'
assert self.params['state'] == 'fake_state' resp = requests.get(url)
assert self.params['code'] == 'fake_code' assert resp.status_code == 200
assert 'Access Granted' in resp.body.decode() assert OAuthHandler.params['error'] == 'access_denied'
assert 'denied access' in resp.text
def test_oauth_handler_error(oauth_server):
url = oauth_server.url + '?error=fake'
resp = requests.get(url)
assert resp.status_code == 200
assert OAuthHandler.params['error'] == 'fake'
assert 'fake' in resp.text
def test_oauth_handler_success(oauth_server):
url = oauth_server.url + '?state=fake_state&code=fake_code'
resp = requests.get(url)
assert resp.status_code == 200
assert OAuthHandler.params['error'] is None
assert OAuthHandler.params['state'] == 'fake_state'
assert OAuthHandler.params['code'] == 'fake_code'
assert 'Access Granted' in resp.text
def test_oauth_terminal_non_mobile_authorize(reddit, terminal, config): def test_oauth_terminal_non_mobile_authorize(reddit, terminal, config):
@@ -66,11 +75,11 @@ def test_oauth_terminal_mobile_authorize(reddit, terminal, config):
assert '.compact' in oauth.reddit.config.API_PATHS['authorize'] assert '.compact' in oauth.reddit.config.API_PATHS['authorize']
def test_oauth_authorize_with_refresh_token(oauth, stdscr, refresh_token): def test_oauth_authorize_with_refresh_token(oauth, refresh_token):
oauth.config.refresh_token = refresh_token oauth.config.refresh_token = refresh_token
oauth.authorize() oauth.authorize()
assert oauth.http_server is None assert oauth.server is None
# We should be able to handle an oauth failure # We should be able to handle an oauth failure
with mock.patch.object(oauth.reddit, 'refresh_access_information'): with mock.patch.object(oauth.reddit, 'refresh_access_information'):
@@ -78,7 +87,15 @@ def test_oauth_authorize_with_refresh_token(oauth, stdscr, refresh_token):
oauth.reddit.refresh_access_information.side_effect = exception oauth.reddit.refresh_access_information.side_effect = exception
oauth.authorize() oauth.authorize()
assert isinstance(oauth.term.loader.exception, OAuthException) assert isinstance(oauth.term.loader.exception, OAuthException)
assert oauth.http_server is None assert oauth.server is None
def test_oauth_clear_data(oauth):
oauth.config.refresh_token = 'secrettoken'
oauth.reddit.refresh_token = 'secrettoken'
oauth.clear_oauth_data()
assert oauth.config.refresh_token is None
assert oauth.reddit.refresh_token is None
def test_oauth_authorize(oauth, reddit, stdscr, refresh_token): def test_oauth_authorize(oauth, reddit, stdscr, refresh_token):
@@ -87,34 +104,36 @@ def test_oauth_authorize(oauth, reddit, stdscr, refresh_token):
# function in the destination oauth module and not the helpers module # function in the destination oauth module and not the helpers module
with mock.patch('uuid.UUID.hex', new_callable=mock.PropertyMock) as uuid, \ with mock.patch('uuid.UUID.hex', new_callable=mock.PropertyMock) as uuid, \
mock.patch('rtv.terminal.Terminal.open_browser') as open_browser, \ mock.patch('rtv.terminal.Terminal.open_browser') as open_browser, \
mock.patch('rtv.oauth.ioloop') as ioloop, \ mock.patch('rtv.oauth.HTTPServer') as http_server, \
mock.patch('rtv.oauth.httpserver'), \
mock.patch.object(oauth.reddit, 'user'), \ mock.patch.object(oauth.reddit, 'user'), \
mock.patch('time.sleep'): mock.patch('time.sleep'):
io = ioloop.IOLoop.current.return_value
# Valid authorization # Valid authorization
oauth.term._display = False oauth.term._display = False
params = {'state': 'uniqueid', 'code': 'secretcode', 'error': None} params = {'state': 'uniqueid', 'code': 'secretcode', 'error': None}
uuid.return_value = params['state'] uuid.return_value = params['state']
io.start.side_effect = lambda *_: oauth.params.update(**params)
def serve_forever():
oauth.params.update(**params)
http_server.return_value.serve_forever.side_effect = serve_forever
oauth.authorize() oauth.authorize()
assert not open_browser.called assert open_browser.called
oauth.reddit.get_access_information.assert_called_with( oauth.reddit.get_access_information.assert_called_with(
reddit, params['code']) reddit, params['code'])
assert oauth.config.refresh_token is not None assert oauth.config.refresh_token is not None
assert oauth.config.save_refresh_token.called assert oauth.config.save_refresh_token.called
stdscr.reset_mock() stdscr.reset_mock()
oauth.reddit.get_access_information.reset_mock() oauth.reddit.get_access_information.reset_mock()
oauth.config.save_refresh_token.reset_mock() oauth.config.save_refresh_token.reset_mock()
oauth.http_server = None oauth.server = None
# The next authorization should skip the oauth process # The next authorization should skip the oauth process
oauth.config.refresh_token = refresh_token oauth.config.refresh_token = refresh_token
oauth.authorize() oauth.authorize()
assert oauth.reddit.user is not None assert oauth.reddit.user is not None
assert oauth.http_server is None assert oauth.server is None
stdscr.reset_mock() stdscr.reset_mock()
# Invalid state returned # Invalid state returned
@@ -129,7 +148,6 @@ def test_oauth_authorize(oauth, reddit, stdscr, refresh_token):
oauth.term._display = True oauth.term._display = True
params = {'state': 'uniqueid', 'code': 'secretcode', 'error': None} params = {'state': 'uniqueid', 'code': 'secretcode', 'error': None}
uuid.return_value = params['state'] uuid.return_value = params['state']
io.start.side_effect = lambda *_: oauth.params.update(**params)
oauth.authorize() oauth.authorize()
assert open_browser.called assert open_browser.called
@@ -137,11 +155,12 @@ def test_oauth_authorize(oauth, reddit, stdscr, refresh_token):
reddit, params['code']) reddit, params['code'])
assert oauth.config.refresh_token is not None assert oauth.config.refresh_token is not None
assert oauth.config.save_refresh_token.called assert oauth.config.save_refresh_token.called
stdscr.reset_mock() stdscr.reset_mock()
oauth.reddit.get_access_information.reset_mock() oauth.reddit.get_access_information.reset_mock()
oauth.config.refresh_token = None oauth.config.refresh_token = None
oauth.config.save_refresh_token.reset_mock() oauth.config.save_refresh_token.reset_mock()
oauth.http_server = None oauth.server = None
# Exceptions when logging in are handled correctly # Exceptions when logging in are handled correctly
with mock.patch.object(oauth.reddit, 'get_access_information'): with mock.patch.object(oauth.reddit, 'get_access_information'):
@@ -149,13 +168,4 @@ def test_oauth_authorize(oauth, reddit, stdscr, refresh_token):
oauth.reddit.get_access_information.side_effect = exception oauth.reddit.get_access_information.side_effect = exception
oauth.authorize() oauth.authorize()
assert isinstance(oauth.term.loader.exception, OAuthException) assert isinstance(oauth.term.loader.exception, OAuthException)
assert not oauth.config.save_refresh_token.called assert not oauth.config.save_refresh_token.called
def test_oauth_clear_data(oauth):
oauth.config.refresh_token = 'secrettoken'
oauth.reddit.refresh_token = 'secrettoken'
oauth.clear_oauth_data()
assert oauth.config.refresh_token is None
assert oauth.reddit.refresh_token is None

View File

@@ -3,6 +3,7 @@ from __future__ import unicode_literals
import time import time
import curses import curses
from collections import OrderedDict
import six import six
import pytest import pytest
@@ -314,7 +315,13 @@ def test_objects_controller_command():
ControllerA.character_map = {Command('REFRESH'): 0, Command('UPVOTE'): 0} ControllerA.character_map = {Command('REFRESH'): 0, Command('UPVOTE'): 0}
# A double command can't share the first key with a single comand # A double command can't share the first key with a single comand
keymap = KeyMap({'REFRESH': ['gg'], 'UPVOTE': ['g']}) keymap = KeyMap(OrderedDict([('REFRESH', ['gg']), ('UPVOTE', ['g'])]))
with pytest.raises(exceptions.ConfigError) as e:
ControllerA(None, keymap=keymap)
assert 'ControllerA' in six.text_type(e)
# It doesn't matter which order they were entered
keymap = KeyMap(OrderedDict([('UPVOTE', ['g']), ('REFRESH', ['gg'])]))
with pytest.raises(exceptions.ConfigError) as e: with pytest.raises(exceptions.ConfigError) as e:
ControllerA(None, keymap=keymap) ControllerA(None, keymap=keymap)
assert 'ControllerA' in six.text_type(e) assert 'ControllerA' in six.text_type(e)

View File

@@ -22,6 +22,21 @@ def test_submission_page_construct(reddit, terminal, config, oauth):
# Toggle the second comment so we can check the draw more comments method # Toggle the second comment so we can check the draw more comments method
page.content.toggle(1) page.content.toggle(1)
# Set some special flags to make sure that we can draw them
submission_data = page.content.get(-1)
submission_data['gold'] = True
submission_data['stickied'] = True
submission_data['saved'] = True
submission_data['flair'] = 'flair'
# Set some special flags to make sure that we can draw them
comment_data = page.content.get(0)
comment_data['gold'] = True
comment_data['stickied'] = True
comment_data['saved'] = True
comment_data['flair'] = 'flair'
page.draw() page.draw()
# Title # Title
@@ -70,6 +85,14 @@ def test_submission_refresh(submission_page):
submission_page.refresh_content() submission_page.refresh_content()
def test_submission_exit(submission_page):
# Exiting should set active to false
submission_page.active = True
submission_page.controller.trigger('h')
assert not submission_page.active
def test_submission_unauthenticated(submission_page, terminal): def test_submission_unauthenticated(submission_page, terminal):
# Unauthenticated commands # Unauthenticated commands
@@ -179,6 +202,16 @@ def test_submission_vote(submission_page, refresh_token):
assert upvote.called assert upvote.called
assert data['likes'] is True assert data['likes'] is True
# Clear vote
submission_page.controller.trigger('a')
assert clear_vote.called
assert data['likes'] is None
# Upvote
submission_page.controller.trigger('a')
assert upvote.called
assert data['likes'] is True
# Downvote # Downvote
submission_page.controller.trigger('z') submission_page.controller.trigger('z')
assert downvote.called assert downvote.called
@@ -271,7 +304,6 @@ def test_submission_comment(submission_page, terminal, refresh_token):
mock.patch.object(terminal, 'open_editor') as open_editor, \ mock.patch.object(terminal, 'open_editor') as open_editor, \
mock.patch('time.sleep'): mock.patch('time.sleep'):
open_editor.return_value.__enter__.return_value = 'comment text' open_editor.return_value.__enter__.return_value = 'comment text'
submission_page.controller.trigger('c') submission_page.controller.trigger('c')
assert open_editor.called assert open_editor.called
add_comment.assert_called_with('comment text') add_comment.assert_called_with('comment text')

View File

@@ -195,12 +195,24 @@ def test_subreddit_open_subscriptions(subreddit_page, refresh_token):
subreddit_page.config.refresh_token = refresh_token subreddit_page.config.refresh_token = refresh_token
subreddit_page.oauth.authorize() subreddit_page.oauth.authorize()
# Open a subscription # Open subscriptions
with mock.patch('rtv.page.Page.loop') as loop: with mock.patch('rtv.page.Page.loop') as loop:
subreddit_page.controller.trigger('s') subreddit_page.controller.trigger('s')
assert loop.called assert loop.called
def test_subreddit_open_multireddits(subreddit_page, refresh_token):
# Log in
subreddit_page.config.refresh_token = refresh_token
subreddit_page.oauth.authorize()
# Open multireddits
with mock.patch('rtv.page.Page.loop') as loop:
subreddit_page.controller.trigger('S')
assert loop.called
def test_subreddit_draw_header(subreddit_page, refresh_token, terminal): def test_subreddit_draw_header(subreddit_page, refresh_token, terminal):
# /r/front alias should be renamed in the header # /r/front alias should be renamed in the header

View File

@@ -389,6 +389,7 @@ def test_open_link_subprocess(terminal):
with mock.patch('time.sleep'), \ with mock.patch('time.sleep'), \
mock.patch('os.system'), \ mock.patch('os.system'), \
mock.patch('subprocess.Popen') as Popen, \
mock.patch('six.moves.input') as six_input, \ mock.patch('six.moves.input') as six_input, \
mock.patch.object(terminal, 'get_mailcap_entry'): mock.patch.object(terminal, 'get_mailcap_entry'):
@@ -398,6 +399,9 @@ def test_open_link_subprocess(terminal):
six_input.reset_mock() six_input.reset_mock()
os.system.reset_mock() os.system.reset_mock()
terminal.stdscr.subwin.addstr.reset_mock() terminal.stdscr.subwin.addstr.reset_mock()
Popen.return_value.communicate.return_value = '', 'stderr message'
Popen.return_value.poll.return_value = 0
Popen.return_value.wait.return_value = 0
def get_error(): def get_error():
# Check if an error message was printed to the terminal # Check if an error message was printed to the terminal
@@ -415,6 +419,8 @@ def test_open_link_subprocess(terminal):
# Non-blocking failure # Non-blocking failure
reset_mock() reset_mock()
Popen.return_value.poll.return_value = 127
Popen.return_value.wait.return_value = 127
entry = ('fake .', 'fake %s') entry = ('fake .', 'fake %s')
terminal.get_mailcap_entry.return_value = entry terminal.get_mailcap_entry.return_value = entry
terminal.open_link(url) terminal.open_link(url)
@@ -431,6 +437,8 @@ def test_open_link_subprocess(terminal):
# needsterminal failure # needsterminal failure
reset_mock() reset_mock()
Popen.return_value.poll.return_value = 127
Popen.return_value.wait.return_value = 127
entry = ('fake .', 'fake %s; needsterminal') entry = ('fake .', 'fake %s; needsterminal')
terminal.get_mailcap_entry.return_value = entry terminal.get_mailcap_entry.return_value = entry
terminal.open_link(url) terminal.open_link(url)
@@ -447,6 +455,8 @@ def test_open_link_subprocess(terminal):
# copiousoutput failure # copiousoutput failure
reset_mock() reset_mock()
Popen.return_value.poll.return_value = 127
Popen.return_value.wait.return_value = 127
entry = ('fake .', 'fake %s; needsterminal; copiousoutput') entry = ('fake .', 'fake %s; needsterminal; copiousoutput')
terminal.get_mailcap_entry.return_value = entry terminal.get_mailcap_entry.return_value = entry
terminal.open_link(url) terminal.open_link(url)
@@ -495,3 +505,40 @@ def test_open_pager(terminal, stdscr):
terminal.open_pager(data) terminal.open_pager(data)
message = 'Could not open pager fake'.encode('ascii') message = 'Could not open pager fake'.encode('ascii')
assert stdscr.addstr.called_with(0, 0, message) assert stdscr.addstr.called_with(0, 0, message)
def test_open_urlview(terminal, stdscr):
data = "Hello World! ❤"
def side_effect(args, stdin=None):
assert stdin is not None
raise OSError
with mock.patch('subprocess.Popen') as Popen, \
mock.patch.dict('os.environ', {'RTV_URLVIEWER': 'fake'}):
Popen.return_value.poll.return_value = 0
terminal.open_urlview(data)
assert Popen.called
assert not stdscr.addstr.called
Popen.return_value.poll.return_value = 1
terminal.open_urlview(data)
assert stdscr.subwin.addstr.called
# Raise an OS error
Popen.side_effect = side_effect
terminal.open_urlview(data)
message = 'Failed to open fake'.encode('utf-8')
assert stdscr.addstr.called_with(0, 0, message)
def test_strip_textpad(terminal):
assert terminal.strip_textpad(None) is None
assert terminal.strip_textpad(' foo ') == ' foo'
text = 'alpha bravo\ncharlie \ndelta \n echo \n\nfoxtrot\n\n\n'
assert terminal.strip_textpad(text) == (
'alpha bravocharlie delta\n echo\n\nfoxtrot')