Extract auth code to own file, add some tests - toot - Unnamed repository; edit… | |
Log | |
Files | |
Refs | |
LICENSE | |
--- | |
commit a50ffe62c33698b9a1bf1211149cdfb1aaeca48e | |
parent 787e0d28b4cd9d77d7948da80874d17942e9c330 | |
Author: Ivan Habunek <[email protected]> | |
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(-) | |
--- | |
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 | |
+# | |
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 | |
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 instanc… | |
+ | |
+ base_url = 'https://' + instance | |
+ | |
+ app = App(instance, base_url, response['client_id'], response['client_secr… | |
+ 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_IN… | |
+ 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… | |
+your account. When you do, you will be given an <yellow>authorization code</ye… | |
+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']) | |
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… | |
from toot.output import print_out, print_instance, print_account, print_search… | |
-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 instanc… | |
- | |
- base_url = 'https://' + instance | |
- | |
- app = App(instance, base_url, response['client_id'], response['client_secr… | |
- 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_IN… | |
- 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_hy… | |
@@ -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… | |
-your account. When you do, you will be given an <yellow>authorization code</ye… | |
-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>") |