;; This Source Code Form is subject to the terms of the Mozilla Public ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; ;; Copyright (c) KALEIDOS INC (ns backend-tests.rpc-profile-test (:require [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] [app.email.blacklist :as email.blacklist] [app.email.whitelist :as email.whitelist] [app.rpc :as-alias rpc] [app.rpc.commands.profile :as profile] [app.tokens :as tokens] [app.util.time :as dt] [backend-tests.helpers :as th] [clojure.java.io :as io] [clojure.test :as t] [cuerdas.core :as str] [datoteka.fs :as fs] [mockery.core :refer [with-mocks]])) ;; TODO: profile deletion with teams ;; TODO: profile deletion with owner teams (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) (t/deftest clean-email (t/is "foo@example.com" (profile/clean-email "mailto:foo@example.com")) (t/is "foo@example.com" (profile/clean-email "mailto:")) (t/is "foo@example.com" (profile/clean-email "")) (t/is "foo@example.com" (profile/clean-email "foo@example.com>")) (t/is "foo@example.com" (profile/clean-email " (th/db-get :profile {:email "user@example.com"}) (profile/decode-row))] (t/is (= "penpot" (:auth-backend profile))) (t/is (= "foobar" (:fullname profile))) (t/is (false? (:is-active profile))) (t/is (uuid? (:default-team-id profile))) (t/is (uuid? (:default-project-id profile))) (let [props (:props profile)] (t/is (= "utma" (:penpot/utm-campaign props))) (t/is (= "mtma" (:penpot/mtm-campaign props))))))) (t/deftest prepare-register-and-register-profile-2 (with-mocks [mock {:target 'app.email/send! :return nil}] (let [current-token (atom nil)] ;; PREPARE REGISTER (let [data {::th/type :prepare-register-profile :email "hello@example.com" :password "foobar"} out (th/command! data) token (get-in out [:result :token])] (t/is (th/success? out)) (reset! current-token token)) ;; DO REGISTRATION (let [data {::th/type :register-profile :token @current-token :fullname "foobar" :accept-terms-and-privacy true :accept-newsletter-subscription true} out (th/command! data)] (t/is (nil? (:error out))) (t/is (= 1 (:call-count @mock)))) (th/reset-mock! mock) ;; PREPARE REGISTER: second attempt (let [data {::th/type :prepare-register-profile :email "hello@example.com" :password "foobar"} out (th/command! data) token (get-in out [:result :token])] (t/is (th/success? out)) (reset! current-token token)) ;; DO REGISTRATION: second attempt (let [data {::th/type :register-profile :token @current-token :fullname "foobar" :accept-terms-and-privacy true :accept-newsletter-subscription true} out (th/command! data)] (t/is (nil? (:error out))) (t/is (= 0 (:call-count @mock)))) (with-mocks [_ {:target 'app.rpc.commands.auth/elapsed-verify-threshold? :return true}] ;; DO REGISTRATION: third attempt (let [data {::th/type :register-profile :token @current-token :fullname "foobar" :accept-terms-and-privacy true :accept-newsletter-subscription true} out (th/command! data)] (t/is (nil? (:error out))) (t/is (= 1 (:call-count @mock)))))))) (t/deftest prepare-register-and-register-profile-3 (with-mocks [mock {:target 'app.email/send! :return nil}] (let [current-token (atom nil)] ;; PREPARE REGISTER (let [data {::th/type :prepare-register-profile :email "hello@example.com" :password "foobar"} out (th/command! data) token (get-in out [:result :token])] (t/is (th/success? out)) (reset! current-token token)) ;; DO REGISTRATION (let [data {::th/type :register-profile :token @current-token :fullname "foobar" :accept-terms-and-privacy true :accept-newsletter-subscription true} out (th/command! data)] (t/is (nil? (:error out))) (t/is (= 1 (:call-count @mock)))) (th/reset-mock! mock) (th/db-update! :profile {:is-blocked true} {:email "hello@example.com"}) ;; PREPARE REGISTER: second attempt (let [data {::th/type :prepare-register-profile :email "hello@example.com" :password "foobar"} out (th/command! data) token (get-in out [:result :token])] (t/is (th/success? out)) (reset! current-token token)) (with-mocks [_ {:target 'app.rpc.commands.auth/elapsed-verify-threshold? :return true}] ;; DO REGISTRATION: second attempt (let [data {::th/type :register-profile :token @current-token :fullname "foobar" :accept-terms-and-privacy true :accept-newsletter-subscription true} out (th/command! data)] (t/is (nil? (:error out))) (t/is (= 0 (:call-count @mock)))))))) (t/deftest prepare-and-register-with-invitation-and-enabled-registration-1 (let [sprops (:app.setup/props th/*system*) itoken (tokens/generate sprops {:iss :team-invitation :exp (dt/in-future "48h") :role :editor :team-id uuid/zero :member-email "user@example.com"}) data {::th/type :prepare-register-profile :invitation-token itoken :email "user@example.com" :password "foobar"} {:keys [result error] :as out} (th/command! data)] (t/is (nil? error)) (t/is (map? result)) (t/is (string? (:token result))) (let [rtoken (:token result) data {::th/type :register-profile :token rtoken :fullname "foobar"} {:keys [result error] :as out} (th/command! data)] ;; (th/print-result! out) (t/is (nil? error)) (t/is (map? result)) (t/is (string? (:invitation-token result)))))) (t/deftest prepare-and-register-with-invitation-and-enabled-registration-2 (let [sprops (:app.setup/props th/*system*) itoken (tokens/generate sprops {:iss :team-invitation :exp (dt/in-future "48h") :role :editor :team-id uuid/zero :member-email "user2@example.com"}) data {::th/type :prepare-register-profile :invitation-token itoken :email "user@example.com" :password "foobar"} out (th/command! data)] (t/is (not (th/success? out))) (let [edata (-> out :error ex-data)] (t/is (= :restriction (:type edata))) (t/is (= :email-does-not-match-invitation (:code edata)))))) (t/deftest prepare-and-register-with-invitation-and-disabled-registration-1 (with-redefs [app.config/flags [:disable-registration]] (let [sprops (:app.setup/props th/*system*) itoken (tokens/generate sprops {:iss :team-invitation :exp (dt/in-future "48h") :role :editor :team-id uuid/zero :member-email "user@example.com"}) data {::th/type :prepare-register-profile :invitation-token itoken :email "user@example.com" :password "foobar"} out (th/command! data)] (t/is (not (th/success? out))) (let [edata (-> out :error ex-data)] (t/is (= :restriction (:type edata))) (t/is (= :registration-disabled (:code edata))))))) (t/deftest prepare-and-register-with-invitation-and-disabled-registration-2 (with-redefs [app.config/flags [:disable-registration]] (let [sprops (:app.setup/props th/*system*) itoken (tokens/generate sprops {:iss :team-invitation :exp (dt/in-future "48h") :role :editor :team-id uuid/zero :member-email "user2@example.com"}) data {::th/type :prepare-register-profile :invitation-token itoken :email "user@example.com" :password "foobar"} out (th/command! data)] (t/is (not (th/success? out))) (let [edata (-> out :error ex-data)] (t/is (= :restriction (:type edata))) (t/is (= :registration-disabled (:code edata))))))) (t/deftest prepare-and-register-with-invitation-and-disabled-login-with-password (with-redefs [app.config/flags [:disable-login-with-password]] (let [sprops (:app.setup/props th/*system*) itoken (tokens/generate sprops {:iss :team-invitation :exp (dt/in-future "48h") :role :editor :team-id uuid/zero :member-email "user2@example.com"}) data {::th/type :prepare-register-profile :invitation-token itoken :email "user@example.com" :password "foobar"} out (th/command! data)] (t/is (not (th/success? out))) (let [edata (-> out :error ex-data)] (t/is (= :restriction (:type edata))) (t/is (= :registration-disabled (:code edata))))))) (t/deftest prepare-register-with-registration-disabled (with-redefs [app.config/flags #{}] (let [data {::th/type :prepare-register-profile :email "user@example.com" :password "foobar"} out (th/command! data)] (t/is (not (th/success? out))) (let [edata (-> out :error ex-data)] (t/is (= :restriction (:type edata))) (t/is (= :registration-disabled (:code edata))))))) (t/deftest prepare-register-with-existing-user (let [profile (th/create-profile* 1) data {::th/type :prepare-register-profile :email (:email profile) :password "foobar"} out (th/command! data)] ;; (th/print-result! out) (t/is (th/success? out)) (let [result (:result out)] (t/is (contains? result :token))))) (t/deftest prepare-register-profile-with-bounced-email (let [pool (:app.db/pool th/*system*) data {::th/type :prepare-register-profile :email "user@example.com" :password "foobar"}] (th/create-global-complaint-for pool {:type :bounce :email "user@example.com"}) (let [out (th/command! data)] (t/is (not (th/success? out))) (let [edata (-> out :error ex-data)] (t/is (= :restriction (:type edata))) (t/is (= :email-has-permanent-bounces (:code edata))))))) (t/deftest register-profile-with-complained-email (let [pool (:app.db/pool th/*system*) data {::th/type :prepare-register-profile :email "user@example.com" :password "foobar"}] (th/create-global-complaint-for pool {:type :complaint :email "user@example.com"}) (let [out (th/command! data)] (t/is (not (th/success? out))) (let [edata (-> out :error ex-data)] (t/is (= :restriction (:type edata))) (t/is (= :email-has-complaints (:code edata))))))) (t/deftest register-profile-with-email-as-password (let [data {::th/type :prepare-register-profile :email "user@example.com" :password "USER@example.com"} out (th/command! data)] (t/is (not (th/success? out))) (let [edata (-> out :error ex-data)] (t/is (= :validation (:type edata))) (t/is (= :email-as-password (:code edata)))))) (t/deftest email-change-request (with-mocks [mock {:target 'app.email/send! :return nil}] (let [profile (th/create-profile* 1) pool (:app.db/pool th/*system*) data {::th/type :request-email-change ::rpc/profile-id (:id profile) :email "user1@example.com"}] ;; without complaints (let [out (th/command! data)] ;; (th/print-result! out) (t/is (nil? (:result out))) (let [mock @mock] (t/is (= 1 (:call-count mock))) (t/is (true? (:called? mock))))) ;; with complaints (th/create-global-complaint-for pool {:type :complaint :email (:email data)}) (let [out (th/command! data)] ;; (th/print-result! out) (t/is (nil? (:result out))) (let [edata (-> out :error ex-data)] (t/is (= :restriction (:type edata))) (t/is (= :email-has-complaints (:code edata)))) (t/is (= 1 (:call-count @mock)))) ;; with bounces (th/create-global-complaint-for pool {:type :bounce :email (:email data)}) (let [out (th/command! data)] ;; (th/print-result! out) (let [edata (-> out :error ex-data)] (t/is (= :restriction (:type edata))) (t/is (= :email-has-permanent-bounces (:code edata)))) (t/is (= 1 (:call-count @mock))))))) (t/deftest email-change-request-without-smtp (with-mocks [mock {:target 'app.email/send! :return nil}] (with-redefs [app.config/flags #{}] (let [profile (th/create-profile* 1) pool (:app.db/pool th/*system*) data {::th/type :request-email-change ::rpc/profile-id (:id profile) :email "user1@example.com"} out (th/command! data)] ;; (th/print-result! out) (t/is (false? (:called? @mock))) (let [res (:result out)] (t/is (= {:changed true} res))))))) (t/deftest request-profile-recovery (with-mocks [mock {:target 'app.email/send! :return nil}] (let [profile1 (th/create-profile* 1 {:is-active false}) profile2 (th/create-profile* 2 {:is-active true}) pool (:app.db/pool th/*system*) data {::th/type :request-profile-recovery}] ;; with invalid email (let [data (assoc data :email "foo@bar.com") out (th/command! data)] (t/is (nil? (:result out))) (t/is (= 0 (:call-count @mock)))) ;; with valid email inactive user (let [data (assoc data :email (:email profile1)) out (th/command! data)] (t/is (= 0 (:call-count @mock))) (t/is (nil? (:result out))) (t/is (nil? (:error out)))) (with-mocks [_ {:target 'app.rpc.commands.auth/elapsed-verify-threshold? :return true}] ;; with valid email inactive user (let [data (assoc data :email (:email profile1)) out (th/command! data)] (t/is (= 1 (:call-count @mock))) (t/is (nil? (:result out))) (t/is (nil? (:error out))))) (th/reset-mock! mock) ;; with valid email and active user (with-mocks [_ {:target 'app.rpc.commands.auth/elapsed-verify-threshold? :return true}] (let [data (assoc data :email (:email profile2)) out (th/command! data)] ;; (th/print-result! out) (t/is (nil? (:result out))) (t/is (= 1 (:call-count @mock)))) ;; with valid email and active user with global complaints (th/create-global-complaint-for pool {:type :complaint :email (:email profile2)}) (let [data (assoc data :email (:email profile2)) out (th/command! data)] ;; (th/print-result! out) (t/is (nil? (:result out))) (t/is (= 1 (:call-count @mock)))) ;; with valid email and active user with global bounce (th/create-global-complaint-for pool {:type :bounce :email (:email profile2)}) (let [data (assoc data :email (:email profile2)) out (th/command! data)] (t/is (nil? (:result out))) (t/is (nil? (:error out))) ;; (th/print-result! out) (t/is (= 1 (:call-count @mock)))))))) (t/deftest update-profile-password (let [profile (th/create-profile* 1) data {::th/type :update-profile-password ::rpc/profile-id (:id profile) :old-password "123123" :password "foobarfoobar"} out (th/command! data)] (t/is (nil? (:error out))) (t/is (nil? (:result out))))) (t/deftest update-profile-password-bad-old-password (let [profile (th/create-profile* 1) data {::th/type :update-profile-password ::rpc/profile-id (:id profile) :old-password "badpassword" :password "foobarfoobar"} {:keys [result error] :as out} (th/command! data)] (t/is (th/ex-info? error)) (t/is (th/ex-of-type? error :validation)) (t/is (th/ex-of-code? error :old-password-not-match)))) (t/deftest update-profile-password-email-as-password (let [profile (th/create-profile* 1) data {::th/type :update-profile-password ::rpc/profile-id (:id profile) :old-password "123123" :password "profile1.test@nodomain.com"} {:keys [result error] :as out} (th/command! data)] (t/is (th/ex-info? error)) (t/is (th/ex-of-type? error :validation)) (t/is (th/ex-of-code? error :email-as-password))))