diff options
-rw-r--r-- | README.org | 32 | ||||
-rw-r--r-- | fixture/client.plstore | 5 | ||||
-rw-r--r-- | lisp/mastodon-auth.el | 201 | ||||
-rw-r--r-- | lisp/mastodon-client.el | 110 | ||||
-rw-r--r-- | lisp/mastodon-http.el | 8 | ||||
-rw-r--r-- | lisp/mastodon.el | 30 | ||||
-rw-r--r-- | test/ert-helper.el | 1 | ||||
-rw-r--r-- | test/mastodon-auth-tests.el | 52 | ||||
-rw-r--r-- | test/mastodon-client-tests.el | 101 |
9 files changed, 392 insertions, 148 deletions
@@ -94,10 +94,6 @@ This repo also incorporates fixes for two bugs that were never merged into the u - https://github.com/jdenen/mastodon.el/issues/227 (and https://github.com/jdenen/mastodon.el/issues/234) - https://github.com/jdenen/mastodon.el/issues/228 -** 2FA - -It looks like 2-factor auth was never completed in the original repo. It's not a priority for me, auth ain't my thing. If you want to hack on it, its on the develop branch in the original repo. - ** contributing Contributions are welcome. Feel free to open an issue or get in touch with me on mastodon: @@ -166,21 +162,31 @@ Or, with =use-package=: #+END_SRC ** Usage -*** 2 Factor Auth -2FA is not supported yet. It is in the [[https://github.com/jdenen/mastodon.el/milestone/2][plans]] for the =1.0.0= release. - -If you have 2FA enabled and try to use mastodon.el, your Emacs client will hang until you `C-g` your way out. *** Instance -Set =mastodon-instance-url= in your =.emacs= or =customize=. Defaults to the [[https://mastodon.social][flagship]]. +You need to set 2 variables in your init file to get started: + +1. mastodon-instance-url +2. mastodon-active-user + +(see their doc strings for details). For example If you want to post +toots as "example_user@social.instance.org", then put this in your init +file: #+BEGIN_SRC emacs-lisp - (setq mastodon-instance-url "https://my.instance.url") + (setq mastodon-instance-url "https://social.instance.org" + mastodon-active-user "example_user") #+END_SRC -There is an option to have your user credentials (email address and password) saved to disk so you don't have to re-enter them on every restart. -The default is not to do this because if not properly configured it would save these unencrypted which is not a good default to have. -Customize the variable =mastodon-auth-source-file= if you want to enable this feature. +Then *restart* Emacs and run =M-x mastodon=. Make sure you are connected +to internet before you do this. If you have multiple mastodon accounts +you can activate one at a time by changing those two variables and +restarting Emacs. + +If you have been using mastodon.el before this change and the above +steps do not work it's advisable that you delete the old file specified +by =mastodon-client--token-file= and restart Emacs and follow the steps +again. *** Timelines diff --git a/fixture/client.plstore b/fixture/client.plstore index e050018..48d951c 100644 --- a/fixture/client.plstore +++ b/fixture/client.plstore @@ -1,3 +1,6 @@ ;;; public entries -*- mode: plstore -*- (("mastodon-http://other.example" :client_id "id1" :client_secret "secret1") - ("mastodon-http://mastodon.example" :client_id "id2" :client_secret "secret2")) + ("mastodon-http://mastodon.example" :client_id "id2" :client_secret "secret2") + ("user-test8000@mastodon.example" :username "test8000@mastodon.example" :instance "http://mastodon.example" :client_id "id2" :client_secret "secret2" :access_token "token2") + ("active-user" :username "test9000@other.example" :instance "http://other.example" :client_id "id1" :client_secret "secret1" :access_token "token1") + ("user-test9000@other.example" :username "test9000@other.example" :instance "http://other.example" :client_id "id1" :client_secret "secret1" :access_token "token1")) diff --git a/lisp/mastodon-auth.el b/lisp/mastodon-auth.el index e582e4f..582f7bb 100644 --- a/lisp/mastodon-auth.el +++ b/lisp/mastodon-auth.el @@ -1,6 +1,7 @@ ;;; mastodon-auth.el --- Auth functions for mastodon.el -*- lexical-binding: t -*- ;; Copyright (C) 2017-2019 Johnson Denen +;; Copyright (C) 2021 Abhiseck Paira <abhiseckpaira@disroot.org> ;; Author: Johnson Denen <johnson.denen@gmail.com> ;; Maintainer: Marty Hiatt <martianhiatus@riseup.net> ;; Version: 0.10.0 @@ -39,21 +40,25 @@ (autoload 'mastodon-http--api "mastodon-http") (autoload 'mastodon-http--get-json "mastodon-http") (autoload 'mastodon-http--post "mastodon-http") +(autoload 'mastodon-http--append-query-string "mastodon-http") +(autoload 'mastodon-client--store-access-token "mastodon-client") +(autoload 'mastodon-client--active-user "mastodon-client") +(autoload 'mastodon-client--make-user-active "mastodon-client") +(autoload 'mastodon-client--form-user-from-vars "mastodon-client") (defvar mastodon-instance-url) +(defvar mastodon-client-scopes) +(defvar mastodon-client-redirect-uri) +(defvar mastodon-active-user) (defgroup mastodon-auth nil "Authenticate with Mastodon." :prefix "mastodon-auth-" :group 'mastodon) -(defcustom mastodon-auth-source-file "" - "Filename to use to store user names and passwords. - -Leave empty to not permanently store any secrets. -Otherwise set to e.g. \"~/.authinfo.gpg\" to have encrypted storage, or -if you are happy with unencryped storage use e.g. \"~/authinfo\"." - :group 'mastodon-auth - :type 'string) +(defvar mastodon-auth-source-file nil + "This variable is obsolete. +This variable currently serves no purpose and will be removed in +the future.") (defvar mastodon-auth--token-alist nil "Alist of User access tokens keyed by instance url.") @@ -61,60 +66,95 @@ if you are happy with unencryped storage use e.g. \"~/authinfo\"." (defvar mastodon-auth--acct-alist nil "Alist of account accts (name@domain) keyed by instance url.") +(defvar mastodon-auth--user-unaware + " ** MASTODON.EL - NOTICE ** + +It appears that you are not aware of the recent developments in +mastodon.el. In short we now require that you also set the +variable `mastodon-active-user' in your init file in addition to +`mastodon-instance-url'. + +Please see its documentation to understand what value it accepts +by running M-x describe-variable on it or visiting our web page: +https://codeberg.org/martianh/mastodon.el + +We apologize for the inconvenience. +") + +(defun mastodon-auth--get-browser-login-url () + "Return properly formed browser login url." + (mastodon-http--append-query-string + (concat mastodon-instance-url "/oauth/authorize/") + `(("response_type" "code") + ("redirect_uri" ,mastodon-client-redirect-uri) + ("scope" ,mastodon-client-scopes) + ("client_id" ,(plist-get (mastodon-client) :client_id))))) + +(defvar mastodon-auth--explanation + (format + " +1. A URL has been copied to your clipboard. Open this URL in a +javascript capable browser and your browser will take you to your +Mastodon instance's login page. + +2. Login to your account (%s) and authorize \"mastodon.el\". + +3. After authorization you will be presented an authorization +code. Copy this code and paste it in the minibuffer prompt." + (mastodon-client--form-user-from-vars))) + +(defun mastodon-auth--show-notice (notice buffer-name &optional ask) + "Display NOTICE to user. +NOTICE is displayed in vertical split occupying 50% of total +width. The buffer name of the buffer being displayed in the +window is BUFFER-NAME. + +When optional argument ASK is given which should be a string, use +ASK as the minibuffer prompt. Return whatever user types in +response to the prompt. + +When ASK is absent return nil." + (let ((buffer (get-buffer-create buffer-name)) + (inhibit-read-only t) + ask-value window) + (set-buffer buffer) + (erase-buffer) + (insert notice) + (fill-region (point-min) (point-max)) + (read-only-mode) + (setq window (select-window + (split-window (frame-root-window) nil 'left) + t)) + (switch-to-buffer buffer t) + (when ask + (setq ask-value (read-string ask)) + (kill-buffer buffer) + (delete-window window)) + ask-value)) + +(defun mastodon-auth--request-authorization-code () + "Ask authorization code and return it." + (let ((url (mastodon-auth--get-browser-login-url)) + authorization-code) + (kill-new url) + (setq authorization-code + (mastodon-auth--show-notice mastodon-auth--explanation + "*mastodon-notice*" + "Authorization Code: ")) + authorization-code)) + (defun mastodon-auth--generate-token () - "Make POST to generate auth token. - -If no auth-sources file, runs -`mastodon-auth--generate-token-no-storing-credentials'. If -auth-sources file exists, runs -`mastodon-auth--generate-token-and-store'." - (if (or (null mastodon-auth-source-file) - (string= "" mastodon-auth-source-file)) - (mastodon-auth--generate-token-no-storing-credentials) - (mastodon-auth--generate-token-and-store))) - -(defun mastodon-auth--generate-token-no-storing-credentials () - "Make POST to generate auth token, without using auth-sources file." - (mastodon-http--post - (concat mastodon-instance-url "/oauth/token") - `(("client_id" . ,(plist-get (mastodon-client) :client_id)) - ("client_secret" . ,(plist-get (mastodon-client) :client_secret)) - ("grant_type" . "password") - ("username" . ,(read-string "Email: " user-mail-address)) - ("password" . ,(read-passwd "Password: ")) - ("scope" . "read write follow")) - nil - :unauthenticated)) - -(defun mastodon-auth--generate-token-and-store () - "Make POST to generate auth token. - -Reads and/or stores secrets in `MASTODON-AUTH-SOURCE-FILE'." - (let* ((auth-sources (list mastodon-auth-source-file)) - (auth-source-creation-prompts - '((user . "Enter email for %h: ") - (secret . "Password: "))) - (credentials-plist (nth 0 (auth-source-search - :create t - :host mastodon-instance-url - :port 443 - :require '(:user :secret))))) - (prog1 - (mastodon-http--post - (concat mastodon-instance-url "/oauth/token") - `(("client_id" . ,(plist-get (mastodon-client) :client_id)) - ("client_secret" . ,(plist-get (mastodon-client) :client_secret)) - ("grant_type" . "password") - ("username" . ,(plist-get credentials-plist :user)) - ("password" . ,(let ((secret (plist-get credentials-plist :secret))) - (if (functionp secret) - (funcall secret) - secret))) - ("scope" . "read write follow")) - nil - :unauthenticated) - (when (functionp (plist-get credentials-plist :save-function)) - (funcall (plist-get credentials-plist :save-function)))))) + "Generate access_token for the user. Return response buffer." + (let ((authorization-code (mastodon-auth--request-authorization-code))) + (mastodon-http--post + (concat mastodon-instance-url "/oauth/token") + `(("grant_type" . "authorization_code") + ("client_secret" . ,(plist-get (mastodon-client) :client_secret)) + ("client_id" . ,(plist-get (mastodon-client) :client_id)) + ("code" . ,authorization-code) + ("redirect_uri" . ,mastodon-client-redirect-uri)) + nil + :unauthenticated))) (defun mastodon-auth--get-token () "Make a request to generate an auth token and return JSON response." @@ -128,16 +168,33 @@ Reads and/or stores secrets in `MASTODON-AUTH-SOURCE-FILE'." (json-read-from-string json-string)))) (defun mastodon-auth--access-token () - "Return exiting or generate new access token. - -If an access token for `mastodon-instance-url' is in -`mastodon-auth--token-alist', return it. - -Otherwise, generate a token and pass it to -`mastodon-auth--handle-token-reponse'." - (if-let ((token (cdr (assoc mastodon-instance-url mastodon-auth--token-alist)))) - token - (mastodon-auth--handle-token-response (mastodon-auth--get-token)))) + "Return the access token to use with `mastodon-instance-url'. + +Generate/save token if none known yet." + (cond (mastodon-auth--token-alist + ;; user variables are known and + ;; initialised already. + (alist-get mastodon-instance-url mastodon-auth--token-alist + nil nil 'equal)) + ((plist-get (mastodon-client--active-user) :access_token) + ;; user variables needs to initialised by reading from + ;; plstore. + (push (cons mastodon-instance-url + (plist-get (mastodon-client--active-user) :access_token)) + mastodon-auth--token-alist) + (alist-get mastodon-instance-url mastodon-auth--token-alist + nil nil 'equal)) + ((null mastodon-active-user) + ;; user not aware of 2FA related changes and has not set the + ;; `mastodon-active-user' properly. Make user aware and error + ;; out. + (mastodon-auth--show-notice mastodon-auth--user-unaware + "*mastodon-notice*") + (error "Variables not set properly")) + (t + ;; user access-token needs to fetched from the server and + ;; stored and variables initialised. + (mastodon-auth--handle-token-response (mastodon-auth--get-token))))) (defun mastodon-auth--handle-token-response (response) "Add token RESPONSE to `mastodon-auth--token-alist'. @@ -148,6 +205,8 @@ Handle any errors from the server." (pcase response ((and (let token (plist-get response :access_token)) (guard token)) + (mastodon-client--make-user-active + (mastodon-client--store-access-token token)) (cdar (push (cons mastodon-instance-url token) mastodon-auth--token-alist))) diff --git a/lisp/mastodon-client.el b/lisp/mastodon-client.el index 42e8b1f..4fc8db7 100644 --- a/lisp/mastodon-client.el +++ b/lisp/mastodon-client.el @@ -1,6 +1,7 @@ ;;; mastodon-client.el --- Client functions for mastodon.el -*- lexical-binding: t -*- ;; Copyright (C) 2017-2019 Johnson Denen +;; Copyright (C) 2021 Abhiseck Paira <abhiseckpaira@disroot.org> ;; Author: Johnson Denen <johnson.denen@gmail.com> ;; Maintainer: Marty Hiatt <martianhiatus@riseup.net> ;; Version: 0.10.0 @@ -32,8 +33,10 @@ (require 'plstore) (require 'json) +(require 'url) (defvar mastodon-instance-url) +(defvar mastodon-active-user) (autoload 'mastodon-http--api "mastodon-http") (autoload 'mastodon-http--post "mastodon-http") @@ -46,14 +49,26 @@ (defvar mastodon-client--client-details-alist nil "An alist of Client id and secrets keyed by the instance url.") +(defvar mastodon-client--active-user-details-plist nil + "A plist of active user details.") + +(defvar mastodon-client-scopes "read write follow" + "Scopes to pass to oauth during registration.") + +(defvar mastodon-client-website "https://codeberg.org/martianh/mastodon.el" + "Website of mastodon.el.") + +(defvar mastodon-client-redirect-uri "urn:ietf:wg:oauth:2.0:oob" + "Redirect_uri as required by oauth.") + (defun mastodon-client--register () "POST client to Mastodon." (mastodon-http--post (mastodon-http--api "apps") - '(("client_name" . "mastodon.el") - ("redirect_uris" . "urn:ietf:wg:oauth:2.0:oob") - ("scopes" . "read write follow") - ("website" . "https://github.com/jdenen/mastodon.el")) + `(("client_name" . "mastodon.el") + ("redirect_uris" . ,mastodon-client-redirect-uri) + ("scopes" . ,mastodon-client-scopes) + ("website" . ,mastodon-client-website)) nil :unauthenticated)) @@ -88,11 +103,96 @@ Make `mastodon-client--fetch' call to determine client values." (plstore-close plstore) client)) +(defun mastodon-client--remove-key-from-plstore (plstore) + "Remove KEY from PLSTORE." + (cdr plstore)) + +;; Actually it returns a plist with client-details if such details are +;; already stored in mastodon.plstore (defun mastodon-client--read () "Retrieve client_id and client_secret from `mastodon-client--token-file'." (let* ((plstore (plstore-open (mastodon-client--token-file))) (mastodon (plstore-get plstore (concat "mastodon-" mastodon-instance-url)))) - (cdr mastodon))) + (mastodon-client--remove-key-from-plstore mastodon))) + +(defun mastodon-client--general-read (key) + "Retrieve the plstore item keyed by KEY. +Return plist without the KEY." + (let* ((plstore (plstore-open (mastodon-client--token-file))) + (plstore-item (plstore-get plstore key))) + (mastodon-client--remove-key-from-plstore plstore-item))) + +(defun mastodon-client--make-user-details-plist () + "Make a plist with current user details. Return it." + `(:username ,(mastodon-client--form-user-from-vars) + :instance ,mastodon-instance-url + :client_id ,(plist-get (mastodon-client) :client_id) + :client_secret ,(plist-get (mastodon-client) :client_secret))) + +(defun mastodon-client--store-access-token (token) + "Save TOKEN as :access_token in plstore of the current user. +Return the plist after the operation." + (let* ((user-details (mastodon-client--make-user-details-plist)) + (plstore (plstore-open (mastodon-client--token-file))) + (username (plist-get user-details :username)) + (plstore-value (setq user-details + (plist-put user-details :access_token token))) + (print-length nil) + (print-level nil)) + (plstore-put plstore (concat "user-" username) plstore-value nil) + (plstore-save plstore) + (plstore-close plstore) + plstore-value)) + +(defun mastodon-client--make-user-active (user-details) + "USER-DETAILS is a plist consisting of user details." + (let ((plstore (plstore-open (mastodon-client--token-file))) + (print-length nil) + (print-level nil)) + (plstore-put plstore "active-user" user-details nil) + (plstore-save plstore) + (plstore-close plstore))) + +(defun mastodon-client--form-user-from-vars () + "Create a username from user variable. Return that username. + +Username in the form user@instance.com is formed from the +variables `mastodon-instance-url' and `mastodon-active-user'." + (concat mastodon-active-user + "@" + (url-host (url-generic-parse-url mastodon-instance-url)))) + +(defun mastodon-client--make-current-user-active () + "Make the user specified by user variables active user. +Return the details (plist)." + (let ((username (mastodon-client--form-user-from-vars)) + user-plist) + (when (setq user-plist + (mastodon-client--general-read (concat "user-" username))) + (mastodon-client--make-user-active user-plist)) + user-plist)) + +(defun mastodon-client--current-user-active-p () + "Return user-details if the current user is active. +Otherwise return nil." + (let ((username (mastodon-client--form-user-from-vars)) + (user-details (mastodon-client--general-read "active-user"))) + (when (and user-details + (equal (plist-get user-details :username) username)) + user-details))) + +(defun mastodon-client--active-user () + "Return the details of the currently active user. + +Details is a plist." + (let ((active-user-details mastodon-client--active-user-details-plist)) + (unless active-user-details + (setq active-user-details + (or (mastodon-client--current-user-active-p) + (mastodon-client--make-current-user-active))) + (setq mastodon-client--active-user-details-plist + active-user-details)) + active-user-details)) (defun mastodon-client () "Return variable client secrets to use for `mastodon-instance-url'. diff --git a/lisp/mastodon-http.el b/lisp/mastodon-http.el index e288c18..35fd070 100644 --- a/lisp/mastodon-http.el +++ b/lisp/mastodon-http.el @@ -32,6 +32,7 @@ (require 'json) (require 'request) ; for attachments upload +(require 'url) (defvar mastodon-instance-url) (defvar mastodon-toot--media-attachment-ids) @@ -156,6 +157,13 @@ Pass response buffer to CALLBACK function." (with-temp-buffer (mastodon-http--url-retrieve-synchronously url)))) +(defun mastodon-http--append-query-string (url params) + "Append PARAMS to URL as query strings and return it. + +PARAMS should be an alist as required `url-build-query-string'." + (let ((query-string (url-build-query-string params))) + (concat url "?" query-string))) + ;; search functions: (defun mastodon-http--process-json-search () "Process JSON returned by a search query to the server." diff --git a/lisp/mastodon.el b/lisp/mastodon.el index 30fc2d3..49abe26 100644 --- a/lisp/mastodon.el +++ b/lisp/mastodon.el @@ -1,6 +1,7 @@ ;;; mastodon.el --- Client for Mastodon -*- lexical-binding: t -*- ;; Copyright (C) 2017-2019 Johnson Denen +;; Copyright (C) 2021 Abhiseck Paira <abhiseckpaira@disroot.org> ;; Author: Johnson Denen <johnson.denen@gmail.com> ;; Maintainer: Marty Hiatt <martianhiatus@riseup.net> ;; Version: 0.10.0 @@ -97,7 +98,34 @@ :group 'external) (defcustom mastodon-instance-url "https://mastodon.social" - "Base URL for the Masto instance from which you toot." + "Base URL for the Mastodon instance you want to be active. + +For example, if your mastodon username is +\"example_user@social.instance.org\", and you want this account +to be active, the value of this variable should be +\"https://social.instance.org\". + +Also for completeness, the value of `mastodon-active-user' should +be \"example_user\". + +After setting these variables you should restart Emacs for these +changes to take effect." + :group 'mastodon + :type 'string) + +(defcustom mastodon-active-user nil + "Username of the active user. + +For example, if your mastodon username is +\"example_user@social.instance.org\", and you want this account +to be active, the value of this variable should be +\"example_user\". + +Also for completeness, the value of `mastodon-instance-url' +should be \"https://social.instance.org\". + +After setting these variables you should restart Emacs for these +changes to take effect." :group 'mastodon :type 'string) diff --git a/test/ert-helper.el b/test/ert-helper.el index c2fd5a6..f65649f 100644 --- a/test/ert-helper.el +++ b/test/ert-helper.el @@ -1,3 +1,4 @@ +(load-file "lisp/mastodon.el") (load-file "lisp/mastodon-search.el") (load-file "lisp/mastodon-async.el") (load-file "lisp/mastodon-http.el") diff --git a/test/mastodon-auth-tests.el b/test/mastodon-auth-tests.el index 6a090b7..2d9d6df 100644 --- a/test/mastodon-auth-tests.el +++ b/test/mastodon-auth-tests.el @@ -32,47 +32,6 @@ `(:error "invalid_grant" :error_description ,error-message)) (t error)))))) -(ert-deftest mastodon-auth--generate-token--no-storing-credentials () - "Should make `mastdon-http--post' request to generate auth token." - (with-mock - (let ((mastodon-auth-source-file "") - (mastodon-instance-url "https://instance.url")) - (mock (mastodon-client) => '(:client_id "id" :client_secret "secret")) - (mock (read-string "Email: " user-mail-address) => "foo@bar.com") - (mock (read-passwd "Password: ") => "password") - (mock (mastodon-http--post "https://instance.url/oauth/token" - '(("client_id" . "id") - ("client_secret" . "secret") - ("grant_type" . "password") - ("username" . "foo@bar.com") - ("password" . "password") - ("scope" . "read write follow")) - nil - :unauthenticated)) - (mastodon-auth--generate-token)))) - -(ert-deftest mastodon-auth--generate-token--storing-credentials () - "Should make `mastdon-http--post' request to generate auth token." - (with-mock - (let ((mastodon-auth-source-file "~/.authinfo") - (mastodon-instance-url "https://instance.url")) - (mock (mastodon-client) => '(:client_id "id" :client_secret "secret")) - (mock (auth-source-search :create t - :host "https://instance.url" - :port 443 - :require '(:user :secret)) - => '((:user "foo@bar.com" :secret (lambda () "password")))) - (mock (mastodon-http--post "https://instance.url/oauth/token" - '(("client_id" . "id") - ("client_secret" . "secret") - ("grant_type" . "password") - ("username" . "foo@bar.com") - ("password" . "password") - ("scope" . "read write follow")) - nil - :unauthenticated)) - (mastodon-auth--generate-token)))) - (ert-deftest mastodon-auth--get-token () "Should generate token and return JSON response." (with-temp-buffer @@ -94,12 +53,23 @@ (ert-deftest mastodon-auth--access-token-not-found () "Should set and return `mastodon-auth--token' if nil." (let ((mastodon-instance-url "https://instance.url") + (mastodon-active-user "user") (mastodon-auth--token-alist nil)) (with-mock (mock (mastodon-auth--get-token) => '(:access_token "foobaz")) + (mock (mastodon-client--store-access-token "foobaz")) + (stub mastodon-client--make-user-active) (should (string= (mastodon-auth--access-token) "foobaz")) (should (equal mastodon-auth--token-alist '(("https://instance.url" . "foobaz"))))))) + +(ert-deftest mastodon-auth--user-unaware () + (let ((mastodon-instance-url "https://instance.url") + (mastodon-active-user nil) + (mastodon-auth--token-alist nil)) + (with-mock + (mock (mastodon-client--active-user)) + (should-error (mastodon-auth--access-token))))) diff --git a/test/mastodon-client-tests.el b/test/mastodon-client-tests.el index 9123286..b302ed6 100644 --- a/test/mastodon-client-tests.el +++ b/test/mastodon-client-tests.el @@ -10,7 +10,7 @@ '(("client_name" . "mastodon.el") ("redirect_uris" . "urn:ietf:wg:oauth:2.0:oob") ("scopes" . "read write follow") - ("website" . "https://github.com/jdenen/mastodon.el")) + ("website" . "https://codeberg.org/martianh/mastodon.el")) nil :unauthenticated)) (mastodon-client--register))) @@ -24,25 +24,22 @@ (current-buffer))) (should (equal (mastodon-client--fetch) '(:foo "bar")))))) -(ert-deftest mastodon-client--store-1 () - "Should return the client plist." +(ert-deftest mastodon-client--store () + "Test the value `mastodon-client--store' returns/stores." (let ((mastodon-instance-url "http://mastodon.example") (plist '(:client_id "id" :client_secret "secret"))) (with-mock (mock (mastodon-client--token-file) => "stubfile.plstore") - (mock (mastodon-client--fetch) => '(:client_id "id" :client_secret "secret")) - (let* ((plstore (plstore-open "stubfile.plstore")) - (client (cdr (plstore-get plstore "mastodon-http://mastodon.example")))) - (should (equal (mastodon-client--store) plist)))))) - -(ert-deftest mastodon-client--store-2 () - "Should store client in `mastodon-client--token-file'." - (let* ((mastodon-instance-url "http://mastodon.example") - (plstore (plstore-open "stubfile.plstore")) - (client (cdr (plstore-get plstore "mastodon-http://mastodon.example")))) - (plstore-close plstore) - (should (string= (plist-get client :client_id) "id")) - (should (string= (plist-get client :client_secret) "secret")))) + (mock (mastodon-client--fetch) => plist) + (should (equal (mastodon-client--store) plist))) + (let* ((plstore (plstore-open "stubfile.plstore")) + (client (mastodon-client--remove-key-from-plstore + (plstore-get plstore "mastodon-http://mastodon.example")))) + (plstore-close plstore) + (should (equal client plist)) + ;; clean up - delete the stubfile + (delete-file "stubfile.plstore")))) + (ert-deftest mastodon-client--read-finds-match () "Should return mastodon client from `mastodon-token-file' if it exists." @@ -52,6 +49,27 @@ (should (equal (mastodon-client--read) '(:client_id "id2" :client_secret "secret2")))))) +(ert-deftest mastodon-client--general-read-finds-match () + (with-mock + (mock (mastodon-client--token-file) => "fixture/client.plstore") + (should (equal (mastodon-client--general-read "user-test8000@mastodon.example") + '(:username "test8000@mastodon.example" + :instance "http://mastodon.example" + :client_id "id2" :client_secret "secret2" + :access_token "token2"))))) + +(ert-deftest mastodon-client--general-read-finds-no-match () + (with-mock + (mock (mastodon-client--token-file) => "fixture/client.plstore") + (should (equal (mastodon-client--general-read "nonexistant-key") + nil)))) + +(ert-deftest mastodon-client--general-read-empty-store () + (with-mock + (mock (mastodon-client--token-file) => "fixture/empty.plstore") + (should (equal (mastodon-client--general-read "something") + nil)))) + (ert-deftest mastodon-client--read-finds-no-match () "Should return mastodon client from `mastodon-token-file' if it exists." (let ((mastodon-instance-url "http://mastodon.social")) @@ -103,3 +121,54 @@ (should (equal (mastodon-client) '(:client_id "foo" :client_secret "baz"))) (should (equal mastodon-client--client-details-alist '(("http://mastodon.example" :client_id "foo" :client_secret "baz"))))))) + +(ert-deftest mastodon-client--form-user-from-vars () + (let ((mastodon-active-user "test9000") + (mastodon-instance-url "https://mastodon.example")) + (should + (equal (mastodon-client--form-user-from-vars) + "test9000@mastodon.example")))) + +(ert-deftest mastodon-client--current-user-active-p () + (let ((mastodon-active-user "test9000") + (mastodon-instance-url "https://mastodon.example")) + ;; when the current user /is/ the active user + (with-mock + (mock (mastodon-client--general-read "active-user") => '(:username "test9000@mastodon.example" :client_id "id1")) + (should (equal (mastodon-client--current-user-active-p) + '(:username "test9000@mastodon.example" :client_id "id1")))) + ;; when the current user is /not/ the active user + (with-mock + (mock (mastodon-client--general-read "active-user") => '(:username "user@other.example" :client_id "id1")) + (should (null (mastodon-client--current-user-active-p)))))) + +(ert-deftest mastodon-client--store-access-token () + (let ((mastodon-instance-url "https://mastodon.example") + (mastodon-active-user "test8000") + (user-details + '(:username "test8000@mastodon.example" + :instance "https://mastodon.example" + :client_id "id" :client_secret "secret" + :access_token "token"))) + ;; test if mastodon-client--store-access-token /returns/ right + ;; value + (with-mock + (mock (mastodon-client) => '(:client_id "id" :client_secret "secret")) + (mock (mastodon-client--token-file) => "stubfile.plstore") + (should (equal (mastodon-client--store-access-token "token") + user-details))) + ;; test if mastodon-client--store-access-token /stores/ right value + (with-mock + (mock (mastodon-client--token-file) => "stubfile.plstore") + (should (equal (mastodon-client--general-read + "user-test8000@mastodon.example") + user-details))) + (delete-file "stubfile.plstore"))) + +(ert-deftest mastodon-client--make-user-active () + (let ((user-details '(:username "test@mastodon.example"))) + (with-mock + (mock (mastodon-client--token-file) => "stubfile.plstore") + (mastodon-client--make-user-active user-details) + (should (equal (mastodon-client--general-read "active-user") + user-details))))) |