mirror of
https://github.com/penpot/penpot.git
synced 2025-08-03 03:18:29 +02:00
🎉 Add automatic complaint and bouncing handling.
This commit is contained in:
parent
17229228a3
commit
7708752ad9
26 changed files with 1073 additions and 73 deletions
|
@ -31,15 +31,24 @@
|
|||
[environ.core :refer [env]]
|
||||
[expound.alpha :as expound]
|
||||
[integrant.core :as ig]
|
||||
[mockery.core :as mk]
|
||||
[promesa.core :as p])
|
||||
(:import org.postgresql.ds.PGSimpleDataSource))
|
||||
|
||||
(def ^:dynamic *system* nil)
|
||||
(def ^:dynamic *pool* nil)
|
||||
|
||||
(def config
|
||||
(merge {:redis-uri "redis://redis/1"
|
||||
:database-uri "postgresql://postgres/penpot_test"
|
||||
:storage-fs-directory "/tmp/app/storage"
|
||||
:migrations-verbose false}
|
||||
cfg/config))
|
||||
|
||||
|
||||
(defn state-init
|
||||
[next]
|
||||
(let [config (-> (main/build-system-config cfg/test-config)
|
||||
(let [config (-> (main/build-system-config config)
|
||||
(dissoc :app.srepl/server
|
||||
:app.http/server
|
||||
:app.http/router
|
||||
|
@ -300,3 +309,31 @@
|
|||
(defn sleep
|
||||
[ms]
|
||||
(Thread/sleep ms))
|
||||
|
||||
(defn mock-config-get-with
|
||||
"Helper for mock app.config/get"
|
||||
[data]
|
||||
(fn
|
||||
([key] (get (merge config data) key))
|
||||
([key default] (get (merge config data) key default))))
|
||||
|
||||
(defn create-complaint-for
|
||||
[conn {:keys [id created-at type]}]
|
||||
(db/insert! conn :profile-complaint-report
|
||||
{:profile-id id
|
||||
:created-at (or created-at (dt/now))
|
||||
:type (name type)
|
||||
:content (db/tjson {})}))
|
||||
|
||||
(defn create-global-complaint-for
|
||||
[conn {:keys [email type created-at]}]
|
||||
(db/insert! conn :global-complaint-report
|
||||
{:email email
|
||||
:type (name type)
|
||||
:created-at (or created-at (dt/now))
|
||||
:content (db/tjson {})}))
|
||||
|
||||
|
||||
(defn reset-mock!
|
||||
[m]
|
||||
(reset! m @(mk/make-mock {})))
|
||||
|
|
316
backend/tests/app/tests/test_bounces_handling.clj
Normal file
316
backend/tests/app/tests/test_bounces_handling.clj
Normal file
|
@ -0,0 +1,316 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2021 UXBOX Labs SL
|
||||
|
||||
(ns app.tests.test-bounces-handling
|
||||
(:require
|
||||
[clojure.pprint :refer [pprint]]
|
||||
[app.http.awsns :as awsns]
|
||||
[app.emails :as emails]
|
||||
[app.tests.helpers :as th]
|
||||
[app.db :as db]
|
||||
[app.util.time :as dt]
|
||||
[mockery.core :refer [with-mocks]]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :once th/state-init)
|
||||
(t/use-fixtures :each th/database-reset)
|
||||
|
||||
;; (with-mocks [mock {:target 'app.tasks/submit! :return nil}]
|
||||
;; Right now we have many different scenarios what can cause a
|
||||
;; bounce/complain report.
|
||||
|
||||
(defn- decode-row
|
||||
[{:keys [content] :as row}]
|
||||
(cond-> row
|
||||
(db/pgobject? content)
|
||||
(assoc :content (db/decode-transit-pgobject content))))
|
||||
|
||||
(defn bounce-report
|
||||
[{:keys [token email] :or {email "user@example.com"}}]
|
||||
{"notificationType" "Bounce",
|
||||
"bounce" {"feedbackId""010701776d7dd251-c08d280d-9f47-41aa-b959-0094fec779d9-000000",
|
||||
"bounceType" "Permanent",
|
||||
"bounceSubType" "General",
|
||||
"bouncedRecipients" [{"emailAddress" email,
|
||||
"action" "failed",
|
||||
"status" "5.1.1",
|
||||
"diagnosticCode" "smtp; 550 5.1.1 user unknown"}]
|
||||
"timestamp" "2021-02-04T14:41:38.000Z",
|
||||
"remoteMtaIp" "22.22.22.22",
|
||||
"reportingMTA" "dsn; b224-13.smtp-out.eu-central-1.amazonses.com"}
|
||||
"mail" {"timestamp" "2021-02-04T14:41:37.020Z",
|
||||
"source" "no-reply@penpot.app",
|
||||
"sourceArn" "arn:aws:ses:eu-central-1:1111111111:identity/penpot.app",
|
||||
"sourceIp" "22.22.22.22",
|
||||
"sendingAccountId" "1111111111",
|
||||
"messageId" "010701776d7dccfc-3c0094e7-01d7-458d-8100-893320186028-000000",
|
||||
"destination" [email],
|
||||
"headersTruncated" false,
|
||||
"headers" [{"name" "Received","value" "from app-pre"},
|
||||
{"name" "Date","value" "Thu, 4 Feb 2021 14:41:36 +0000 (UTC)"},
|
||||
{"name" "From","value" "Penpot <no-reply@penpot.app>"},
|
||||
{"name" "Reply-To","value" "Penpot <no-reply@penpot.app>"},
|
||||
{"name" "To","value" email},
|
||||
{"name" "Message-ID","value" "<2054501.5.1612449696846@penpot.app>"},
|
||||
{"name" "Subject","value" "test"},
|
||||
{"name" "MIME-Version","value" "1.0"},
|
||||
{"name" "Content-Type","value" "multipart/mixed; boundary=\"----=_Part_3_1150363050.1612449696845\""},
|
||||
{"name" "X-Penpot-Data","value" token}],
|
||||
"commonHeaders" {"from" ["Penpot <no-reply@penpot.app>"],
|
||||
"replyTo" ["Penpot <no-reply@penpot.app>"],
|
||||
"date" "Thu, 4 Feb 2021 14:41:36 +0000 (UTC)",
|
||||
"to" [email],
|
||||
"messageId" "<2054501.5.1612449696846@penpot.app>",
|
||||
"subject" "test"}}})
|
||||
|
||||
|
||||
(defn complaint-report
|
||||
[{:keys [token email] :or {email "user@example.com"}}]
|
||||
{"notificationType" "Complaint",
|
||||
"complaint" {"feedbackId" "0107017771528618-dcf4d61f-c889-4c8b-a6ff-6f0b6553b837-000000",
|
||||
"complaintSubType" nil,
|
||||
"complainedRecipients" [{"emailAddress" email}],
|
||||
"timestamp" "2021-02-05T08:32:49.000Z",
|
||||
"userAgent" "Yahoo!-Mail-Feedback/2.0",
|
||||
"complaintFeedbackType" "abuse",
|
||||
"arrivalDate" "2021-02-05T08:31:15.000Z"},
|
||||
"mail" {"timestamp" "2021-02-05T08:31:13.715Z",
|
||||
"source" "no-reply@penpot.app",
|
||||
"sourceArn" "arn:aws:ses:eu-central-1:111111111:identity/penpot.app",
|
||||
"sourceIp" "22.22.22.22",
|
||||
"sendingAccountId" "11111111111",
|
||||
"messageId" "0107017771510f33-a0696d28-859c-4f08-9211-8392d1b5c226-000000",
|
||||
"destination" ["user@yahoo.com"],
|
||||
"headersTruncated" false,
|
||||
"headers" [{"name" "Received","value" "from smtp"},
|
||||
{"name" "Date","value" "Fri, 5 Feb 2021 08:31:13 +0000 (UTC)"},
|
||||
{"name" "From","value" "Penpot <no-reply@penpot.app>"},
|
||||
{"name" "Reply-To","value" "Penpot <no-reply@penpot.app>"},
|
||||
{"name" "To","value" email},
|
||||
{"name" "Message-ID","value" "<1833063698.279.1612513873536@penpot.app>"},
|
||||
{"name" "Subject","value" "Verify email."},
|
||||
{"name" "MIME-Version","value" "1.0"},
|
||||
{"name" "Content-Type","value" "multipart/mixed; boundary=\"----=_Part_276_1174403980.1612513873535\""},
|
||||
{"name" "X-Penpot-Data","value" token}],
|
||||
"commonHeaders" {"from" ["Penpot <no-reply@penpot.app>"],
|
||||
"replyTo" ["Penpot <no-reply@penpot.app>"],
|
||||
"date" "Fri, 5 Feb 2021 08:31:13 +0000 (UTC)",
|
||||
"to" [email],
|
||||
"messageId" "<1833063698.279.1612513873536@penpot.app>",
|
||||
"subject" "Verify email."}}})
|
||||
|
||||
(t/deftest test-parse-bounce-report
|
||||
(let [profile (th/create-profile* 1)
|
||||
tokens (:app.tokens/tokens th/*system*)
|
||||
cfg {:tokens tokens}
|
||||
report (bounce-report {:token (tokens :generate-predefined
|
||||
{:iss :profile-identity
|
||||
:profile-id (:id profile)})})
|
||||
result (#'awsns/parse-notification cfg report)]
|
||||
;; (pprint result)
|
||||
|
||||
(t/is (= "bounce" (:type result)))
|
||||
(t/is (= "permanent" (:kind result)))
|
||||
(t/is (= "general" (:category result)))
|
||||
(t/is (= ["user@example.com"] (mapv :email (:recipients result))))
|
||||
(t/is (= (:id profile) (:profile-id result)))
|
||||
))
|
||||
|
||||
(t/deftest test-parse-complaint-report
|
||||
(let [profile (th/create-profile* 1)
|
||||
tokens (:app.tokens/tokens th/*system*)
|
||||
cfg {:tokens tokens}
|
||||
report (complaint-report {:token (tokens :generate-predefined
|
||||
{:iss :profile-identity
|
||||
:profile-id (:id profile)})})
|
||||
result (#'awsns/parse-notification cfg report)]
|
||||
;; (pprint result)
|
||||
(t/is (= "complaint" (:type result)))
|
||||
(t/is (= "abuse" (:kind result)))
|
||||
(t/is (= nil (:category result)))
|
||||
(t/is (= ["user@example.com"] (into [] (:recipients result))))
|
||||
(t/is (= (:id profile) (:profile-id result)))
|
||||
))
|
||||
|
||||
(t/deftest test-parse-complaint-report-without-token
|
||||
(let [tokens (:app.tokens/tokens th/*system*)
|
||||
cfg {:tokens tokens}
|
||||
report (complaint-report {:token ""})
|
||||
result (#'awsns/parse-notification cfg report)]
|
||||
(t/is (= "complaint" (:type result)))
|
||||
(t/is (= "abuse" (:kind result)))
|
||||
(t/is (= nil (:category result)))
|
||||
(t/is (= ["user@example.com"] (into [] (:recipients result))))
|
||||
(t/is (= nil (:profile-id result)))
|
||||
))
|
||||
|
||||
(t/deftest test-process-bounce-report
|
||||
(let [profile (th/create-profile* 1)
|
||||
tokens (:app.tokens/tokens th/*system*)
|
||||
pool (:app.db/pool th/*system*)
|
||||
cfg {:tokens tokens :pool pool}
|
||||
report (bounce-report {:token (tokens :generate-predefined
|
||||
{:iss :profile-identity
|
||||
:profile-id (:id profile)})})
|
||||
report (#'awsns/parse-notification cfg report)]
|
||||
|
||||
(#'awsns/process-report cfg report)
|
||||
|
||||
(let [rows (->> (db/query pool :profile-complaint-report {:profile-id (:id profile)})
|
||||
(mapv decode-row))]
|
||||
(t/is (= 1 (count rows)))
|
||||
(t/is (= "bounce" (get-in rows [0 :type])))
|
||||
(t/is (= "2021-02-04T14:41:38.000Z" (get-in rows [0 :content :timestamp]))))
|
||||
|
||||
(let [rows (->> (db/query pool :global-complaint-report :all)
|
||||
(mapv decode-row))]
|
||||
(t/is (= 1 (count rows)))
|
||||
(t/is (= "bounce" (get-in rows [0 :type])))
|
||||
(t/is (= "user@example.com" (get-in rows [0 :email]))))
|
||||
|
||||
(let [prof (db/get-by-id pool :profile (:id profile))]
|
||||
(t/is (false? (:is-muted prof))))
|
||||
|
||||
))
|
||||
|
||||
(t/deftest test-process-complaint-report
|
||||
(let [profile (th/create-profile* 1)
|
||||
tokens (:app.tokens/tokens th/*system*)
|
||||
pool (:app.db/pool th/*system*)
|
||||
cfg {:tokens tokens :pool pool}
|
||||
report (complaint-report {:token (tokens :generate-predefined
|
||||
{:iss :profile-identity
|
||||
:profile-id (:id profile)})})
|
||||
report (#'awsns/parse-notification cfg report)]
|
||||
|
||||
(#'awsns/process-report cfg report)
|
||||
|
||||
(let [rows (->> (db/query pool :profile-complaint-report {:profile-id (:id profile)})
|
||||
(mapv decode-row))]
|
||||
(t/is (= 1 (count rows)))
|
||||
(t/is (= "complaint" (get-in rows [0 :type])))
|
||||
(t/is (= "2021-02-05T08:31:15.000Z" (get-in rows [0 :content :timestamp]))))
|
||||
|
||||
|
||||
(let [rows (->> (db/query pool :global-complaint-report :all)
|
||||
(mapv decode-row))]
|
||||
(t/is (= 1 (count rows)))
|
||||
(t/is (= "complaint" (get-in rows [0 :type])))
|
||||
(t/is (= "user@example.com" (get-in rows [0 :email]))))
|
||||
|
||||
|
||||
(let [prof (db/get-by-id pool :profile (:id profile))]
|
||||
(t/is (false? (:is-muted prof))))
|
||||
|
||||
))
|
||||
|
||||
(t/deftest test-process-bounce-report-to-self
|
||||
(let [profile (th/create-profile* 1)
|
||||
tokens (:app.tokens/tokens th/*system*)
|
||||
pool (:app.db/pool th/*system*)
|
||||
cfg {:tokens tokens :pool pool}
|
||||
report (bounce-report {:email (:email profile)
|
||||
:token (tokens :generate-predefined
|
||||
{:iss :profile-identity
|
||||
:profile-id (:id profile)})})
|
||||
report (#'awsns/parse-notification cfg report)]
|
||||
|
||||
(#'awsns/process-report cfg report)
|
||||
|
||||
(let [rows (db/query pool :profile-complaint-report {:profile-id (:id profile)})]
|
||||
(t/is (= 1 (count rows))))
|
||||
|
||||
(let [rows (db/query pool :global-complaint-report :all)]
|
||||
(t/is (= 1 (count rows))))
|
||||
|
||||
(let [prof (db/get-by-id pool :profile (:id profile))]
|
||||
(t/is (true? (:is-muted prof))))))
|
||||
|
||||
(t/deftest test-process-complaint-report-to-self
|
||||
(let [profile (th/create-profile* 1)
|
||||
tokens (:app.tokens/tokens th/*system*)
|
||||
pool (:app.db/pool th/*system*)
|
||||
cfg {:tokens tokens :pool pool}
|
||||
report (complaint-report {:email (:email profile)
|
||||
:token (tokens :generate-predefined
|
||||
{:iss :profile-identity
|
||||
:profile-id (:id profile)})})
|
||||
report (#'awsns/parse-notification cfg report)]
|
||||
|
||||
(#'awsns/process-report cfg report)
|
||||
|
||||
(let [rows (db/query pool :profile-complaint-report {:profile-id (:id profile)})]
|
||||
(t/is (= 1 (count rows))))
|
||||
|
||||
(let [rows (db/query pool :global-complaint-report :all)]
|
||||
(t/is (= 1 (count rows))))
|
||||
|
||||
(let [prof (db/get-by-id pool :profile (:id profile))]
|
||||
(t/is (true? (:is-muted prof))))))
|
||||
|
||||
(t/deftest test-allow-send-messages-predicate-with-bounces
|
||||
(with-mocks [mock {:target 'app.config/get
|
||||
:return (th/mock-config-get-with
|
||||
{:profile-bounce-threshold 3
|
||||
:profile-complaint-threshold 2})}]
|
||||
(let [profile (th/create-profile* 1)
|
||||
pool (:app.db/pool th/*system*)]
|
||||
(th/create-complaint-for pool {:type :bounce :id (:id profile) :created-at (dt/in-past {:days 8})})
|
||||
(th/create-complaint-for pool {:type :bounce :id (:id profile)})
|
||||
(th/create-complaint-for pool {:type :bounce :id (:id profile)})
|
||||
|
||||
(t/is (true? (emails/allow-send-emails? pool profile)))
|
||||
(t/is (= 4 (:call-count (deref mock))))
|
||||
|
||||
(th/create-complaint-for pool {:type :bounce :id (:id profile)})
|
||||
(t/is (false? (emails/allow-send-emails? pool profile))))))
|
||||
|
||||
|
||||
(t/deftest test-allow-send-messages-predicate-with-complaints
|
||||
(with-mocks [mock {:target 'app.config/get
|
||||
:return (th/mock-config-get-with
|
||||
{:profile-bounce-threshold 3
|
||||
:profile-complaint-threshold 2})}]
|
||||
(let [profile (th/create-profile* 1)
|
||||
pool (:app.db/pool th/*system*)]
|
||||
(th/create-complaint-for pool {:type :bounce :id (:id profile) :created-at (dt/in-past {:days 8})})
|
||||
(th/create-complaint-for pool {:type :bounce :id (:id profile) :created-at (dt/in-past {:days 8})})
|
||||
(th/create-complaint-for pool {:type :bounce :id (:id profile)})
|
||||
(th/create-complaint-for pool {:type :bounce :id (:id profile)})
|
||||
(th/create-complaint-for pool {:type :complaint :id (:id profile)})
|
||||
|
||||
(t/is (true? (emails/allow-send-emails? pool profile)))
|
||||
(t/is (= 4 (:call-count (deref mock))))
|
||||
|
||||
(th/create-complaint-for pool {:type :complaint :id (:id profile)})
|
||||
(t/is (false? (emails/allow-send-emails? pool profile))))))
|
||||
|
||||
(t/deftest test-has-complaint-reports-predicate
|
||||
(let [profile (th/create-profile* 1)
|
||||
pool (:app.db/pool th/*system*)]
|
||||
|
||||
(t/is (false? (emails/has-complaint-reports? pool (:email profile))))
|
||||
|
||||
(th/create-global-complaint-for pool {:type :bounce :email (:email profile)})
|
||||
(t/is (false? (emails/has-complaint-reports? pool (:email profile))))
|
||||
|
||||
(th/create-global-complaint-for pool {:type :complaint :email (:email profile)})
|
||||
(t/is (true? (emails/has-complaint-reports? pool (:email profile))))))
|
||||
|
||||
(t/deftest test-has-bounce-reports-predicate
|
||||
(let [profile (th/create-profile* 1)
|
||||
pool (:app.db/pool th/*system*)]
|
||||
|
||||
(t/is (false? (emails/has-bounce-reports? pool (:email profile))))
|
||||
|
||||
(th/create-global-complaint-for pool {:type :complaint :email (:email profile)})
|
||||
(t/is (false? (emails/has-bounce-reports? pool (:email profile))))
|
||||
|
||||
(th/create-global-complaint-for pool {:type :bounce :email (:email profile)})
|
||||
(t/is (true? (emails/has-bounce-reports? pool (:email profile))))))
|
|
@ -11,7 +11,6 @@
|
|||
(:require
|
||||
[clojure.test :as t]
|
||||
[promesa.core :as p]
|
||||
[mockery.core :refer [with-mock]]
|
||||
[app.db :as db]
|
||||
[app.emails :as emails]
|
||||
[app.tests.helpers :as th]))
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
[app.rpc.mutations.profile :as profile]
|
||||
[app.tests.helpers :as th]))
|
||||
|
||||
;; 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)
|
||||
|
||||
|
@ -187,11 +190,6 @@
|
|||
(t/testing "not allowed email domain"
|
||||
(t/is (false? (profile/email-domain-in-whitelist? whitelist "username@somedomain.com"))))))
|
||||
|
||||
;; TODO: profile deletion with teams
|
||||
;; TODO: profile deletion with owner teams
|
||||
;; TODO: profile registration
|
||||
;; TODO: profile password recovery
|
||||
|
||||
(t/deftest test-register-when-registration-disabled
|
||||
(with-mocks [mock {:target 'app.config/get
|
||||
:return (th/mock-config-get-with
|
||||
|
@ -267,8 +265,7 @@
|
|||
(t/is (= (:code edata) :email-has-permanent-bounces))))))
|
||||
|
||||
(t/deftest test-register-profile-with-complained-email
|
||||
(with-mocks [mock {:target 'app.emails/send!
|
||||
:return nil}]
|
||||
(with-mocks [mock {:target 'app.emails/send! :return nil}]
|
||||
(let [pool (:app.db/pool th/*system*)
|
||||
data {::th/type :register-profile
|
||||
:email "user@example.com"
|
||||
|
@ -282,3 +279,86 @@
|
|||
|
||||
(let [result (:result out)]
|
||||
(t/is (= (:email data) (:email result)))))))
|
||||
|
||||
(t/deftest test-email-change-request
|
||||
(with-mocks [mock {:target 'app.emails/send! :return nil}]
|
||||
(let [profile (th/create-profile* 1)
|
||||
pool (:app.db/pool th/*system*)
|
||||
data {::th/type :request-email-change
|
||||
:profile-id (:id profile)
|
||||
:email "user1@example.com"}]
|
||||
|
||||
;; without complaints
|
||||
(let [out (th/mutation! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:result out)))
|
||||
(let [mock (deref 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/mutation! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:result out)))
|
||||
(t/is (= 2 (:call-count (deref mock)))))
|
||||
|
||||
;; with bounces
|
||||
(th/create-global-complaint-for pool {:type :bounce :email (:email data)})
|
||||
(let [out (th/mutation! data)
|
||||
error (:error out)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (th/ex-of-type? error :validation))
|
||||
(t/is (th/ex-of-code? error :email-has-permanent-bounces))
|
||||
(t/is (= 2 (:call-count (deref mock))))))))
|
||||
|
||||
(t/deftest test-request-profile-recovery
|
||||
(with-mocks [mock {:target 'app.emails/send! :return nil}]
|
||||
(let [profile1 (th/create-profile* 1)
|
||||
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/mutation! data)]
|
||||
(t/is (nil? (:result out)))
|
||||
(t/is (= 0 (:call-count (deref mock)))))
|
||||
|
||||
;; with valid email inactive user
|
||||
(let [data (assoc data :email (:email profile1))
|
||||
out (th/mutation! data)
|
||||
error (:error out)]
|
||||
(t/is (= 0 (:call-count (deref mock))))
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (th/ex-of-type? error :validation))
|
||||
(t/is (th/ex-of-code? error :profile-not-verified)))
|
||||
|
||||
;; with valid email and active user
|
||||
(let [data (assoc data :email (:email profile2))
|
||||
out (th/mutation! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:result out)))
|
||||
(t/is (= 1 (:call-count (deref 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/mutation! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:result out)))
|
||||
(t/is (= 2 (:call-count (deref 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/mutation! data)
|
||||
error (:error out)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (= 2 (:call-count (deref mock))))
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (th/ex-of-type? error :validation))
|
||||
(t/is (th/ex-of-code? error :email-has-permanent-bounces)))
|
||||
|
||||
)))
|
||||
|
|
88
backend/tests/app/tests/test_services_teams.clj
Normal file
88
backend/tests/app/tests/test_services_teams.clj
Normal file
|
@ -0,0 +1,88 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.tests.test-services-teams
|
||||
(:require
|
||||
[app.common.uuid :as uuid]
|
||||
[app.db :as db]
|
||||
[app.http :as http]
|
||||
[app.storage :as sto]
|
||||
[app.tests.helpers :as th]
|
||||
[mockery.core :refer [with-mocks]]
|
||||
[clojure.test :as t]
|
||||
[datoteka.core :as fs]))
|
||||
|
||||
(t/use-fixtures :once th/state-init)
|
||||
(t/use-fixtures :each th/database-reset)
|
||||
|
||||
(t/deftest test-invite-team-member
|
||||
(with-mocks [mock {:target 'app.emails/send! :return nil}]
|
||||
(let [profile1 (th/create-profile* 1 {:is-active true})
|
||||
profile2 (th/create-profile* 2 {:is-active true})
|
||||
profile3 (th/create-profile* 3 {:is-active true :is-muted true})
|
||||
|
||||
team (th/create-team* 1 {:profile-id (:id profile1)})
|
||||
|
||||
pool (:app.db/pool th/*system*)
|
||||
data {::th/type :invite-team-member
|
||||
:team-id (:id team)
|
||||
:role :editor
|
||||
:profile-id (:id profile1)}]
|
||||
|
||||
;; (th/print-result! out)
|
||||
|
||||
;; invite external user without complaints
|
||||
(let [data (assoc data :email "foo@bar.com")
|
||||
out (th/mutation! data)]
|
||||
(t/is (nil? (:result out)))
|
||||
(t/is (= 1 (:call-count (deref mock)))))
|
||||
|
||||
;; invite internal user without complaints
|
||||
(th/reset-mock! mock)
|
||||
(let [data (assoc data :email (:email profile2))
|
||||
out (th/mutation! data)]
|
||||
(t/is (nil? (:result out)))
|
||||
(t/is (= 1 (:call-count (deref mock)))))
|
||||
|
||||
;; invite user with complaint
|
||||
(th/create-global-complaint-for pool {:type :complaint :email "foo@bar.com"})
|
||||
(th/reset-mock! mock)
|
||||
(let [data (assoc data :email "foo@bar.com")
|
||||
out (th/mutation! data)]
|
||||
(t/is (nil? (:result out)))
|
||||
(t/is (= 1 (:call-count (deref mock)))))
|
||||
|
||||
;; invite user with bounce
|
||||
(th/reset-mock! mock)
|
||||
(th/create-global-complaint-for pool {:type :bounce :email "foo@bar.com"})
|
||||
(let [data (assoc data :email "foo@bar.com")
|
||||
out (th/mutation! data)
|
||||
error (:error out)]
|
||||
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (th/ex-of-type? error :validation))
|
||||
(t/is (th/ex-of-code? error :email-has-permanent-bounces))
|
||||
(t/is (= 0 (:call-count (deref mock)))))
|
||||
|
||||
;; invite internal user that is muted
|
||||
(th/reset-mock! mock)
|
||||
(let [data (assoc data :email (:email profile3))
|
||||
out (th/mutation! data)
|
||||
error (:error out)]
|
||||
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (th/ex-of-type? error :validation))
|
||||
(t/is (th/ex-of-code? error :member-is-muted))
|
||||
(t/is (= 0 (:call-count (deref mock)))))
|
||||
|
||||
)))
|
||||
|
||||
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue