Extract auth code to own file, add some tests - toot - Unnamed repository; edit this file 'description' to name the repository.
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) LICENSE
       ---
 (DIR) commit a50ffe62c33698b9a1bf1211149cdfb1aaeca48e
 (DIR) parent 787e0d28b4cd9d77d7948da80874d17942e9c330
 (HTM) Author: Ivan Habunek <ivan@habunek.com>
       Date:   Sat, 30 Dec 2017 12:52:55 +0100
       
       Extract auth code to own file, add some tests
       
       Diffstat:
         tests/test_auth.py                  |      57 +++++++++++++++++++++++++++++++
         tests/utils.py                      |       4 ++++
         toot/auth.py                        |     101 +++++++++++++++++++++++++++++++
         toot/commands.py                    |      97 +------------------------------
       
       4 files changed, 165 insertions(+), 94 deletions(-)
       ---
 (DIR) diff --git a/tests/test_auth.py b/tests/test_auth.py
       @@ -0,0 +1,57 @@
       +# -*- coding: utf-8 -*-
       +
       +from toot import App, User, api, config, auth
       +from tests.utils import retval
       +
       +
       +def test_register_app(monkeypatch):
       +    app_data = {'id': 100, 'client_id': 'cid', 'client_secret': 'cs'}
       +
       +    def assert_app(app):
       +        assert isinstance(app, App)
       +        assert app.instance == "foo.bar"
       +        assert app.base_url == "https://foo.bar"
       +        assert app.client_id == "cid"
       +        assert app.client_secret == "cs"
       +
       +    monkeypatch.setattr(api, 'create_app', retval(app_data))
       +    monkeypatch.setattr(config, 'save_app', assert_app)
       +
       +    app = auth.register_app("foo.bar")
       +    assert_app(app)
       +
       +
       +def test_create_app_from_config(monkeypatch):
       +    """When there is saved config, it's returned"""
       +    monkeypatch.setattr(config, 'load_app', retval("loaded app"))
       +    app = auth.create_app_interactive("bezdomni.net")
       +    assert app == 'loaded app'
       +
       +
       +def test_create_app_registered(monkeypatch):
       +    """When there is no saved config, a new app is registered"""
       +    monkeypatch.setattr(config, 'load_app', retval(None))
       +    monkeypatch.setattr(auth, 'register_app', retval("registered app"))
       +
       +    app = auth.create_app_interactive("bezdomni.net")
       +    assert app == 'registered app'
       +
       +
       +def test_create_user(monkeypatch):
       +    app = App(4, 5, 6, 7)
       +
       +    def assert_user(user):
       +        assert isinstance(user, User)
       +        assert user.instance == app.instance
       +        assert user.username == 2
       +        assert user.access_token == 3
       +
       +    monkeypatch.setattr(config, 'save_user', assert_user)
       +
       +    user = auth.create_user(app, 2, 3)
       +
       +    assert_user(user)
       +
       +#
       +# TODO: figure out how to mock input so the rest can be tested
       +#
 (DIR) diff --git a/tests/utils.py b/tests/utils.py
       @@ -10,3 +10,7 @@ class MockResponse:
        
            def json(self):
                return self.response_data
       +
       +
       +def retval(val):
       +    return lambda *args, **kwargs: val
 (DIR) diff --git a/toot/auth.py b/toot/auth.py
       @@ -0,0 +1,101 @@
       +# -*- coding: utf-8 -*-
       +
       +import webbrowser
       +
       +from builtins import input
       +from getpass import getpass
       +
       +from toot import api, config, DEFAULT_INSTANCE, User, App, ConsoleError
       +from toot.output import print_out
       +
       +
       +def register_app(instance):
       +    print_out("Registering application with <green>{}</green>".format(instance))
       +
       +    try:
       +        response = api.create_app(instance)
       +    except Exception:
       +        raise ConsoleError("Registration failed. Did you enter a valid instance?")
       +
       +    base_url = 'https://' + instance
       +
       +    app = App(instance, base_url, response['client_id'], response['client_secret'])
       +    path = config.save_app(app)
       +    print_out("Application tokens saved to: <green>{}</green>\n".format(path))
       +
       +    return app
       +
       +
       +def create_app_interactive(instance=None):
       +    if not instance:
       +        print_out("Choose an instance [<green>{}</green>]: ".format(DEFAULT_INSTANCE), end="")
       +        instance = input()
       +        if not instance:
       +            instance = DEFAULT_INSTANCE
       +
       +    return config.load_app(instance) or register_app(instance)
       +
       +
       +def create_user(app, email, access_token):
       +    user = User(app.instance, email, access_token)
       +    path = config.save_user(user)
       +
       +    print_out("Access token saved to: <green>{}</green>".format(path))
       +
       +    return user
       +
       +
       +def login_interactive(app, email=None):
       +    print_out("Log in to <green>{}</green>".format(app.instance))
       +
       +    if email:
       +        print_out("Email: <green>{}</green>".format(email))
       +
       +    while not email:
       +        email = input('Email: ')
       +
       +    password = getpass('Password: ')
       +
       +    try:
       +        print_out("Authenticating...")
       +        response = api.login(app, email, password)
       +    except api.ApiError:
       +        raise ConsoleError("Login failed")
       +
       +    return create_user(app, email, response['access_token'])
       +
       +
       +BROWSER_LOGIN_EXPLANATION = """
       +This authentication method requires you to log into your Mastodon instance
       +in your browser, where you will be asked to authorize <yellow>toot</yellow> to access
       +your account. When you do, you will be given an <yellow>authorization code</yellow>
       +which you need to paste here.
       +"""
       +
       +
       +def login_browser_interactive(app):
       +    url = api.get_browser_login_url(app)
       +
       +    print_out(BROWSER_LOGIN_EXPLANATION)
       +
       +    print_out("This is the login URL:")
       +    print_out(url)
       +    print_out("")
       +
       +    yesno = input("Open link in default browser? [Y/n]")
       +    if not yesno or yesno.lower() == 'y':
       +        webbrowser.open(url)
       +
       +    authorization_code = ""
       +    while not authorization_code:
       +        authorization_code = input("Authorization code: ")
       +
       +    print_out("\nRequesting access token...")
       +    response = api.request_access_token(app, authorization_code)
       +
       +    # TODO: user email is not available in this workflow, maybe change the User
       +    # to store the username instead? Currently set to "unknown" since it's not
       +    # used anywhere.
       +    email = "unknown"
       +
       +    return create_user(app, email, response['access_token'])
 (DIR) diff --git a/toot/commands.py b/toot/commands.py
       @@ -1,75 +1,16 @@
        # -*- coding: utf-8 -*-
        
       -import webbrowser
       -
        from bs4 import BeautifulSoup
       -from builtins import input
        from datetime import datetime
        from itertools import zip_longest
       -from getpass import getpass
        from itertools import chain
        from textwrap import TextWrapper
        
       -from toot import api, config, DEFAULT_INSTANCE, User, App, ConsoleError
       +from toot import api, config, ConsoleError
       +from toot.auth import login_interactive, login_browser_interactive, create_app_interactive
        from toot.output import print_out, print_instance, print_account, print_search_results
        
        
       -def register_app(instance):
       -    print_out("Registering application with <green>{}</green>".format(instance))
       -
       -    try:
       -        response = api.create_app(instance)
       -    except:
       -        raise ConsoleError("Registration failed. Did you enter a valid instance?")
       -
       -    base_url = 'https://' + instance
       -
       -    app = App(instance, base_url, response['client_id'], response['client_secret'])
       -    path = config.save_app(app)
       -    print_out("Application tokens saved to: <green>{}</green>\n".format(path))
       -
       -    return app
       -
       -
       -def create_app_interactive(instance=None):
       -    if not instance:
       -        print_out("Choose an instance [<green>{}</green>]: ".format(DEFAULT_INSTANCE), end="")
       -        instance = input()
       -        if not instance:
       -            instance = DEFAULT_INSTANCE
       -
       -    return config.load_app(instance) or register_app(instance)
       -
       -
       -def create_user(app, email, access_token):
       -    user = User(app.instance, email, access_token)
       -    path = config.save_user(user)
       -
       -    print_out("Access token saved to: <green>{}</green>".format(path))
       -
       -    return user
       -
       -
       -def login_interactive(app, email=None):
       -    print_out("Log in to <green>{}</green>".format(app.instance))
       -
       -    if email:
       -        print_out("Email: <green>{}</green>".format(email))
       -
       -    while not email:
       -        email = input('Email: ')
       -
       -    password = getpass('Password: ')
       -
       -    try:
       -        print_out("Authenticating...")
       -        response = api.login(app, email, password)
       -    except api.ApiError:
       -        raise ConsoleError("Login failed")
       -
       -    return create_user(app, email, response['access_token'])
       -
       -
        def _print_timeline(item):
            def wrap_text(text, width):
                wrapper = TextWrapper(width=width, break_long_words=False, break_on_hyphens=False)
       @@ -156,41 +97,9 @@ def login(app, user, args):
            print_out("<green>✓ Successfully logged in.</green>")
        
        
       -BROWSER_LOGIN_EXPLANATION = """
       -This authentication method requires you to log into your Mastodon instance
       -in your browser, where you will be asked to authorize <yellow>toot</yellow> to access
       -your account. When you do, you will be given an <yellow>authorization code</yellow>
       -which you need to paste here.
       -"""
       -
       -
        def login_browser(app, user, args):
            app = create_app_interactive(instance=args.instance)
       -    url = api.get_browser_login_url(app)
       -
       -    print_out(BROWSER_LOGIN_EXPLANATION)
       -
       -    print_out("This is the login URL:")
       -    print_out(url)
       -    print_out("")
       -
       -    yesno = input("Open link in default browser? [Y/n]")
       -    if not yesno or yesno.lower() == 'y':
       -        webbrowser.open(url)
       -
       -    authorization_code = ""
       -    while not authorization_code:
       -        authorization_code = input("Authorization code: ")
       -
       -    print_out("\nRequesting access token...")
       -    response = api.request_access_token(app, authorization_code)
       -
       -    # TODO: user email is not available in this workflow, maybe change the User
       -    # to store the username instead? Currently set to "unknown" since it's not
       -    # used anywhere.
       -    email = "unknown"
       -
       -    create_user(app, email, response['access_token'])
       +    login_browser_interactive(app)
        
            print_out()
            print_out("<green>✓ Successfully logged in.</green>")