From dd0d0db764b22fd3ea20b3cdf47ebc895da4b068 Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Mon, 8 Aug 2016 23:05:06 -0700 Subject: [PATCH 1/8] Making real progress this time. --- rtv/__main__.py | 3 - rtv/config.py | 6 +- rtv/docs.py | 24 ++++++++ rtv/oauth.py | 117 ++++++++++++++++++++++----------------- rtv/templates/index.html | 15 +---- setup.cfg | 3 - setup.py | 10 +--- 7 files changed, 96 insertions(+), 82 deletions(-) diff --git a/rtv/__main__.py b/rtv/__main__.py index e13acac..6d94366 100644 --- a/rtv/__main__.py +++ b/rtv/__main__.py @@ -9,7 +9,6 @@ import warnings import six import praw -import tornado import requests from . import docs @@ -158,7 +157,5 @@ def main(): # Ensure sockets are closed to prevent a ResourceWarning if 'reddit' in locals(): reddit.handler.http.close() - # Explicitly close file descriptors opened by Tornado's IOLoop - tornado.ioloop.IOLoop.current().close(all_fds=True) sys.exit(main()) diff --git a/rtv/config.py b/rtv/config.py index 27010bd..5fe3e0e 100644 --- a/rtv/config.py +++ b/rtv/config.py @@ -15,9 +15,9 @@ from .objects import KeyMap PACKAGE = os.path.dirname(__file__) HOME = os.path.expanduser('~') -TEMPLATE = os.path.join(PACKAGE, 'templates') -DEFAULT_CONFIG = os.path.join(TEMPLATE, 'rtv.cfg') -DEFAULT_MAILCAP = os.path.join(TEMPLATE, 'mailcap') +TEMPLATES = os.path.join(PACKAGE, 'templates') +DEFAULT_CONFIG = os.path.join(TEMPLATES, 'rtv.cfg') +DEFAULT_MAILCAP = os.path.join(TEMPLATES, 'mailcap') XDG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.join(HOME, '.config')) CONFIG = os.path.join(XDG_HOME, 'rtv', 'rtv.cfg') MAILCAP = os.path.join(HOME, '.mailcap') diff --git a/rtv/docs.py b/rtv/docs.py index 79513f9..da1a273 100644 --- a/rtv/docs.py +++ b/rtv/docs.py @@ -103,3 +103,27 @@ SUBMISSION_EDIT_FILE = """{content} # # Editing {name} """ + +OAUTH_ACCESS_DENIED = """\ +

Access Denied


+

Reddit Terminal Viewer was + denied access and will continue to operate in unauthenticated mode + you can close this window.

+""" + +OAUTH_ERROR = """\ +

Error


+

{error}

+""" + +OAUTH_INVALID = """\ +

Wait...


+

This page is supposed to be a Reddit OAuth callback. + You can't just come here hands in your pocket!

+""" + +OAUTH_SUCCESS = """\ +

Access Granted


+

Reddit Terminal Viewer + will now log in, you can close this window.

+""" \ No newline at end of file diff --git a/rtv/oauth.py b/rtv/oauth.py index e18fdd7..8147fb3 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -1,62 +1,91 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import os import time import uuid +import string +import codecs +import logging +import threading -from concurrent.futures import ThreadPoolExecutor -from tornado import gen, ioloop, web, httpserver +from urllib.parse import urlparse, parse_qs +from http.server import BaseHTTPRequestHandler, HTTPServer -from .config import TEMPLATE +from . import docs +from .config import TEMPLATES -class OAuthHandler(web.RequestHandler): - """ - Intercepts the redirect that Reddit sends the user to after they verify or - deny the application access. +_logger = logging.getLogger(__name__) - The GET should supply 3 request params: - 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. - """ +INDEX = os.path.join(TEMPLATES, 'index.html') - def initialize(self, display=None, params=None): - self.display = display - self.params = params - def get(self): - 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) +class OAuthHandler(BaseHTTPRequestHandler): - self.render('index.html', **self.params) + params = {'state': None, 'code': None, 'error': None} - complete = self.params['state'] and self.params['code'] - if complete or self.params['error']: - # Stop IOLoop if using a background browser such as firefox - if self.display: - ioloop.IOLoop.current().stop() + def do_GET(self): + + 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) + + # 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 build_body(self, template_file=INDEX): + + 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 + + def log_message(self, format, *args): + _logger.debug(format, *args) class OAuthHelper(object): + params = OAuthHandler.params + def __init__(self, reddit, term, config): self.term = term self.reddit = reddit self.config = config - self.http_server = None - self.params = {'state': None, 'code': None, 'error': 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) + address = ('', self.config['oauth_redirect_port']) + self.server = HTTPServer(address, OAuthHandler) self.reddit.set_oauth_app_info( self.config['oauth_client_id'], @@ -79,14 +108,6 @@ class OAuthHelper(object): self.config.refresh_token) 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 authorize_url = self.reddit.get_authorize_url( state, scope=self.config['oauth_scope'], refreshable=True) @@ -97,7 +118,7 @@ class OAuthHelper(object): # point we continue and check the callback params. with self.term.loader('Opening browser for authorization'): self.term.open_browser(authorize_url) - io.start() + self.server.serve_forever() if self.term.loader.exception: return else: @@ -138,10 +159,4 @@ class OAuthHelper(object): def clear_oauth_data(self): self.reddit.clear_authentication() - 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() \ No newline at end of file + self.config.delete_refresh_token() \ No newline at end of file diff --git a/rtv/templates/index.html b/rtv/templates/index.html index 8c3e275..20a0987 100644 --- a/rtv/templates/index.html +++ b/rtv/templates/index.html @@ -25,20 +25,7 @@ - {% if error == 'access_denied' %} -

Access Denied


-

Reddit Terminal Viewer was denied access and will continue to operate in unauthenticated mode, you can close this window. - {% elif error is not None %} -

Error


-

{{ error }}

- {% elif (state is None or code is None) %} -

Wait...


-

This page is supposed to be a Reddit OAuth callback. You can't just come here hands in your pocket!

- {% else %} -

Access Granted


-

Reddit Terminal Viewer will now log in, you can close this window.

- {% end %} - +${message} diff --git a/setup.cfg b/setup.cfg index a9cf834..bd1f26c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,12 +3,9 @@ universal = 1 [metadata] requires-dist = - tornado praw>=3.5,<4 six requests kitchen beautifulsoup4 mailcap-fix - futures; python_version=="2.6" or python_version=="2.7" - diff --git a/setup.py b/setup.py index 56f34d7..5815f87 100644 --- a/setup.py +++ b/setup.py @@ -3,12 +3,8 @@ import setuptools from version import __version__ as version -requirements = ['tornado', 'praw==3.5.0', 'six', 'requests', 'kitchen', - 'beautifulsoup4', 'mailcap-fix'] - -# Python 2: add required concurrent.futures backport from Python 3.2 -if sys.version_info.major <= 2: - requirements.append('futures') +requirements = ['praw==3.5.0', 'six', 'requests', 'kitchen', 'beautifulsoup4', + 'mailcap-fix'] setuptools.setup( name='rtv', @@ -23,8 +19,6 @@ setuptools.setup( packages=['rtv'], package_data={'rtv': ['templates/*']}, data_files=[("share/man/man1", ["rtv.1"])], - extras_require={ - ':python_version=="2.6" or python_version=="2.7"': ['futures']}, install_requires=requirements, entry_points={'console_scripts': ['rtv=rtv.__main__:main']}, classifiers=[ From dedb0985f7326367cde6700c1ec11904c89ff402 Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Tue, 9 Aug 2016 02:04:48 -0700 Subject: [PATCH 2/8] Everything is working for python 3. --- rtv/docs.py | 2 +- rtv/oauth.py | 78 ++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/rtv/docs.py b/rtv/docs.py index da1a273..6bdf31a 100644 --- a/rtv/docs.py +++ b/rtv/docs.py @@ -107,7 +107,7 @@ SUBMISSION_EDIT_FILE = """{content} OAUTH_ACCESS_DENIED = """\

Access Denied


Reddit Terminal Viewer was - denied access and will continue to operate in unauthenticated mode + denied access and will continue to operate in unauthenticated mode, you can close this window.

""" diff --git a/rtv/oauth.py b/rtv/oauth.py index 8147fb3..a09fccc 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -23,9 +23,23 @@ INDEX = os.path.join(TEMPLATES, 'index.html') class OAuthHandler(BaseHTTPRequestHandler): + # 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 def do_GET(self): + """ + Accepts GET requests to http://localhost:6500/, and stores the query + params in the global dict. If shutdown_on_request is true, stop the + 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 != '/': @@ -46,13 +60,27 @@ class OAuthHandler(BaseHTTPRequestHandler): self.wfile.write(body) - # Shutdown the server after serving the request - # http://stackoverflow.com/a/22533929 - thread = threading.Thread(target=self.server.shutdown) - thread.daemon = True - thread.start() + 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 @@ -70,9 +98,6 @@ class OAuthHandler(BaseHTTPRequestHandler): body = codecs.encode(body, 'utf-8') return body - def log_message(self, format, *args): - _logger.debug(format, *args) - class OAuthHelper(object): @@ -84,8 +109,9 @@ class OAuthHelper(object): self.reddit = reddit self.config = config - address = ('', self.config['oauth_redirect_port']) - self.server = HTTPServer(address, OAuthHandler) + # Wait to initialize the server, we don't want to reserve the port + # unless we know that the server needs to be used. + self.server = None self.reddit.set_oauth_app_info( self.config['oauth_client_id'], @@ -112,28 +138,50 @@ class OAuthHelper(object): authorize_url = self.reddit.get_authorize_url( 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: # Open a background browser (e.g. firefox) which is non-blocking. - # Stop the iloop when the user hits the auth callback, at which - # point we continue and check the callback params. + # The server will block until it responds to its first request, + # at which point we can check the callback params. + OAuthHandler.shutdown_on_request = True with self.term.loader('Opening browser for authorization'): self.term.open_browser(authorize_url) self.server.serve_forever() 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 else: # Open the terminal webbrowser in a background thread and wait # 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 # hit the callback URL. + OAuthHandler.shutdown_on_request = False with self.term.loader('Redirecting to reddit', delay=0): # This load message exists to provide user feedback 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: + _logger.debug('Calling server shutdown()') + self.server.shutdown() + finally: + thread.join() if self.params['error'] == 'access_denied': - self.term.show_notification('Declined access') + self.term.show_notification('Denied access') return elif self.params['error']: self.term.show_notification('Authentication error') From c7a409a192b07545695e6cd3b9d0c249b4507d53 Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Tue, 9 Aug 2016 02:15:40 -0700 Subject: [PATCH 3/8] Got it working for python2 (that was easy) --- rtv/oauth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtv/oauth.py b/rtv/oauth.py index a09fccc..4a65ba2 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -9,8 +9,8 @@ import codecs import logging import threading -from urllib.parse import urlparse, parse_qs -from http.server import BaseHTTPRequestHandler, HTTPServer +from six.moves.urllib.parse import urlparse, parse_qs +from six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from . import docs from .config import TEMPLATES From d4cab22ffe847da41cd38de5dc1e7c6228e44b0d Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Tue, 9 Aug 2016 02:16:07 -0700 Subject: [PATCH 4/8] Remove log message. --- rtv/oauth.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rtv/oauth.py b/rtv/oauth.py index 4a65ba2..1a7171b 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -175,7 +175,6 @@ class OAuthHelper(object): _logger.exception(e) self.term.show_notification('Browser Error') else: - _logger.debug('Calling server shutdown()') self.server.shutdown() finally: thread.join() From c096d7014c3955e34e71af0519865e5a321955b9 Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Tue, 9 Aug 2016 18:26:48 -0700 Subject: [PATCH 5/8] Updating tests. --- tests/conftest.py | 19 ++++++- tests/test_content.py | 5 +- tests/test_oauth.py | 114 ++++++++++++++++++++++------------------- tests/test_terminal.py | 10 ++++ 4 files changed, 94 insertions(+), 54 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f8c2d6b..d78c2de 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,14 +4,16 @@ from __future__ import unicode_literals import os import curses import logging +import threading from functools import partial import praw import pytest from vcr import VCR 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.terminal import Terminal from rtv.subreddit import SubredditPage @@ -196,6 +198,21 @@ def oauth(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() def submission_page(reddit, terminal, config, oauth): submission = 'https://www.reddit.com/r/Python/comments/2xmo63' diff --git a/tests/test_content.py b/tests/test_content.py index e7c6452..bcc0d5f 100644 --- a/tests/test_content.py +++ b/tests/test_content.py @@ -6,13 +6,16 @@ from itertools import islice import six import praw -import mock import pytest from rtv.content import ( Content, SubmissionContent, SubredditContent, SubscriptionContent) from rtv import exceptions +try: + from unittest import mock +except ImportError: + import mock # Test entering a bunch of text into the prompt # (text, parsed subreddit, parsed order) diff --git a/tests/test_oauth.py b/tests/test_oauth.py index 25332dc..f6f4f7c 100644 --- a/tests/test_oauth.py +++ b/tests/test_oauth.py @@ -1,12 +1,11 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from tornado.web import Application -from tornado.testing import AsyncHTTPTestCase +import requests from praw.errors import OAuthException from rtv.oauth import OAuthHelper, OAuthHandler -from rtv.config import TEMPLATE + try: from unittest import mock @@ -14,38 +13,48 @@ except ImportError: import mock -class TestAuthHandler(AsyncHTTPTestCase): +def test_oauth_handler_not_found(oauth_server): - def get_app(self): - self.params = {} - handler = [('/', OAuthHandler, {'params': self.params})] - return Application(handler, template_path=TEMPLATE) + url = oauth_server.url + 'favicon.ico' + resp = requests.get(url) + assert resp.status_code == 404 - 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): - 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_oauth_handler_no_callback(oauth_server): - def test_error(self): - resp = self.fetch('/?error=fake') - assert resp.code == 200 - assert self.params['error'] == 'fake' - assert 'fake' in resp.body.decode() + resp = requests.get(oauth_server.url) + assert resp.status_code == 200 + assert 'Wait...' in resp.text + assert OAuthHandler.params['error'] is None - def test_success(self): - resp = self.fetch('/?state=fake_state&code=fake_code') - assert resp.code == 200 - assert self.params['error'] is None - assert self.params['state'] == 'fake_state' - assert self.params['code'] == 'fake_code' - assert 'Access Granted' in resp.body.decode() + +def test_oauth_handler_access_denied(oauth_server): + + url = oauth_server.url + '?error=access_denied' + resp = requests.get(url) + assert resp.status_code == 200 + 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): @@ -66,11 +75,11 @@ def test_oauth_terminal_mobile_authorize(reddit, terminal, config): 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.authorize() - assert oauth.http_server is None + assert oauth.server is None # We should be able to handle an oauth failure 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.authorize() 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): @@ -87,34 +104,36 @@ def test_oauth_authorize(oauth, reddit, stdscr, refresh_token): # function in the destination oauth module and not the helpers module 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.oauth.ioloop') as ioloop, \ - mock.patch('rtv.oauth.httpserver'), \ + mock.patch('rtv.oauth.HTTPServer') as http_server, \ mock.patch.object(oauth.reddit, 'user'), \ mock.patch('time.sleep'): - io = ioloop.IOLoop.current.return_value # Valid authorization oauth.term._display = False params = {'state': 'uniqueid', 'code': 'secretcode', 'error': None} 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() - assert not open_browser.called + assert open_browser.called oauth.reddit.get_access_information.assert_called_with( reddit, params['code']) assert oauth.config.refresh_token is not None assert oauth.config.save_refresh_token.called + stdscr.reset_mock() oauth.reddit.get_access_information.reset_mock() oauth.config.save_refresh_token.reset_mock() - oauth.http_server = None + oauth.server = None # The next authorization should skip the oauth process oauth.config.refresh_token = refresh_token oauth.authorize() assert oauth.reddit.user is not None - assert oauth.http_server is None + assert oauth.server is None stdscr.reset_mock() # Invalid state returned @@ -129,7 +148,6 @@ def test_oauth_authorize(oauth, reddit, stdscr, refresh_token): oauth.term._display = True params = {'state': 'uniqueid', 'code': 'secretcode', 'error': None} uuid.return_value = params['state'] - io.start.side_effect = lambda *_: oauth.params.update(**params) oauth.authorize() assert open_browser.called @@ -137,11 +155,12 @@ def test_oauth_authorize(oauth, reddit, stdscr, refresh_token): reddit, params['code']) assert oauth.config.refresh_token is not None assert oauth.config.save_refresh_token.called + stdscr.reset_mock() oauth.reddit.get_access_information.reset_mock() oauth.config.refresh_token = None oauth.config.save_refresh_token.reset_mock() - oauth.http_server = None + oauth.server = None # Exceptions when logging in are handled correctly 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.authorize() assert isinstance(oauth.term.loader.exception, OAuthException) - 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 \ No newline at end of file + assert not oauth.config.save_refresh_token.called \ No newline at end of file diff --git a/tests/test_terminal.py b/tests/test_terminal.py index 86cfff5..0d5701a 100644 --- a/tests/test_terminal.py +++ b/tests/test_terminal.py @@ -389,6 +389,7 @@ def test_open_link_subprocess(terminal): with mock.patch('time.sleep'), \ mock.patch('os.system'), \ + mock.patch('subprocess.Popen') as Popen, \ mock.patch('six.moves.input') as six_input, \ mock.patch.object(terminal, 'get_mailcap_entry'): @@ -398,6 +399,9 @@ def test_open_link_subprocess(terminal): six_input.reset_mock() os.system.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(): # Check if an error message was printed to the terminal @@ -415,6 +419,8 @@ def test_open_link_subprocess(terminal): # Non-blocking failure reset_mock() + Popen.return_value.poll.return_value = 127 + Popen.return_value.wait.return_value = 127 entry = ('fake .', 'fake %s') terminal.get_mailcap_entry.return_value = entry terminal.open_link(url) @@ -431,6 +437,8 @@ def test_open_link_subprocess(terminal): # needsterminal failure reset_mock() + Popen.return_value.poll.return_value = 127 + Popen.return_value.wait.return_value = 127 entry = ('fake .', 'fake %s; needsterminal') terminal.get_mailcap_entry.return_value = entry terminal.open_link(url) @@ -447,6 +455,8 @@ def test_open_link_subprocess(terminal): # copiousoutput failure reset_mock() + Popen.return_value.poll.return_value = 127 + Popen.return_value.wait.return_value = 127 entry = ('fake .', 'fake %s; needsterminal; copiousoutput') terminal.get_mailcap_entry.return_value = entry terminal.open_link(url) From 17425008b31a84b54673b0351d7aaf2a00b070a8 Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Wed, 10 Aug 2016 01:32:02 -0700 Subject: [PATCH 6/8] Adding general test coverage. --- rtv/terminal.py | 2 +- .../test_subreddit_open_multireddits.yaml | 963 ++++++++++++++++++ tests/test_config.py | 24 + tests/test_objects.py | 9 +- tests/test_submission.py | 34 +- tests/test_subreddit.py | 14 +- tests/test_terminal.py | 37 + 7 files changed, 1079 insertions(+), 4 deletions(-) create mode 100644 tests/cassettes/test_subreddit_open_multireddits.yaml diff --git a/rtv/terminal.py b/rtv/terminal.py index d6c0976..4cad9e1 100644 --- a/rtv/terminal.py +++ b/rtv/terminal.py @@ -728,7 +728,7 @@ class Terminal(object): # space, assume that a newline operation was intended by the user stack, current_line = [], '' for line in text.split('\n'): - if line.endswith(' '): + if line.endswith(' ') or not line: stack.append(current_line + line.rstrip()) current_line = '' else: diff --git a/tests/cassettes/test_subreddit_open_multireddits.yaml b/tests/cassettes/test_subreddit_open_multireddits.yaml new file mode 100644 index 0000000..5c107ca --- /dev/null +++ b/tests/cassettes/test_subreddit_open_multireddits.yaml @@ -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 diff --git a/tests/test_config.py b/tests/test_config.py index 6cda44a..199281f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -25,6 +25,25 @@ def test_copy_default_config(): 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(): "Make sure the example mailcap file was included in the package" @@ -180,6 +199,11 @@ def test_config_refresh_token(): def test_config_history(): "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: config = Config(history_file=fp.name, history_size=3) diff --git a/tests/test_objects.py b/tests/test_objects.py index a658091..1ac11ca 100644 --- a/tests/test_objects.py +++ b/tests/test_objects.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import time import curses +from collections import OrderedDict import six import pytest @@ -314,7 +315,13 @@ def test_objects_controller_command(): ControllerA.character_map = {Command('REFRESH'): 0, Command('UPVOTE'): 0} # 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: ControllerA(None, keymap=keymap) assert 'ControllerA' in six.text_type(e) diff --git a/tests/test_submission.py b/tests/test_submission.py index c21434b..6127693 100644 --- a/tests/test_submission.py +++ b/tests/test_submission.py @@ -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 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() # Title @@ -70,6 +85,14 @@ def test_submission_refresh(submission_page): 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): # Unauthenticated commands @@ -179,6 +202,16 @@ def test_submission_vote(submission_page, refresh_token): assert upvote.called 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 submission_page.controller.trigger('z') 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('time.sleep'): open_editor.return_value.__enter__.return_value = 'comment text' - submission_page.controller.trigger('c') assert open_editor.called add_comment.assert_called_with('comment text') diff --git a/tests/test_subreddit.py b/tests/test_subreddit.py index 09519b5..6a73048 100644 --- a/tests/test_subreddit.py +++ b/tests/test_subreddit.py @@ -195,12 +195,24 @@ def test_subreddit_open_subscriptions(subreddit_page, refresh_token): subreddit_page.config.refresh_token = refresh_token subreddit_page.oauth.authorize() - # Open a subscription + # Open subscriptions with mock.patch('rtv.page.Page.loop') as loop: subreddit_page.controller.trigger('s') 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): # /r/front alias should be renamed in the header diff --git a/tests/test_terminal.py b/tests/test_terminal.py index 0d5701a..3d837d7 100644 --- a/tests/test_terminal.py +++ b/tests/test_terminal.py @@ -505,3 +505,40 @@ def test_open_pager(terminal, stdscr): terminal.open_pager(data) message = 'Could not open pager fake'.encode('ascii') 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') From a7255b541fe9eac592004f21540685ae8df0ce91 Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Wed, 10 Aug 2016 01:37:12 -0700 Subject: [PATCH 7/8] Updating travis vcrpy dependency. --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 081d022..80cc630 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,7 @@ python: - "3.4" - "3.5" before_install: - - pip install coveralls pytest coverage mock pylint - - pip install git+https://github.com/kevin1024/vcrpy.git + - pip install coveralls pytest coverage mock pylint vcrpy install: - pip install . script: From f8584a30fa53f9a99b308c94633845d1837c644c Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Wed, 10 Aug 2016 01:51:45 -0700 Subject: [PATCH 8/8] Disable pylint input error for six.moves. --- rtv/oauth.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rtv/oauth.py b/rtv/oauth.py index 1a7171b..b78e7f7 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -9,6 +9,7 @@ import codecs import logging import threading +#pylint: disable=import-error from six.moves.urllib.parse import urlparse, parse_qs from six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer