mirror of
https://github.com/penpot/penpot.git
synced 2025-01-09 08:20:45 -05:00
db689d151e
- makes the profile access more efficient (replace in-app joins to a simple select query on profile table - add partial support for access-tokens (still missing some RPC methods) - move router definitions to specific modules and simplify the main http module definitions to simple includes - simplifiy authentication code related to access-tokens and sessions - normalize db parameters with proper namespaced props - more work on convert all modules initialization to use proper specs with fully-qualified keyword config props
467 lines
14 KiB
Clojure
467 lines
14 KiB
Clojure
;; 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.helpers
|
|
(:require
|
|
[app.auth]
|
|
[app.common.data :as d]
|
|
[app.common.flags :as flags]
|
|
[app.common.pages :as cp]
|
|
[app.common.pprint :as pp]
|
|
[app.common.spec :as us]
|
|
[app.common.uuid :as uuid]
|
|
[app.config :as cf]
|
|
[app.db :as db]
|
|
[app.main :as main]
|
|
[app.media :as-alias mtx]
|
|
[app.media]
|
|
[app.migrations]
|
|
[app.msgbus :as-alias mbus]
|
|
[app.rpc :as-alias rpc]
|
|
[app.rpc.commands.auth :as cmd.auth]
|
|
[app.rpc.commands.files :as files]
|
|
[app.rpc.commands.files.create :as files.create]
|
|
[app.rpc.commands.files.update :as files.update]
|
|
[app.rpc.commands.teams :as teams]
|
|
[app.rpc.helpers :as rph]
|
|
[app.rpc.mutations.profile :as profile]
|
|
[app.util.blob :as blob]
|
|
[app.util.services :as sv]
|
|
[app.util.time :as dt]
|
|
[clojure.java.io :as io]
|
|
[clojure.spec.alpha :as s]
|
|
[clojure.test :as t]
|
|
[cuerdas.core :as str]
|
|
[datoteka.core :as fs]
|
|
[environ.core :refer [env]]
|
|
[expound.alpha :as expound]
|
|
[integrant.core :as ig]
|
|
[mockery.core :as mk]
|
|
[promesa.core :as p]
|
|
[yetti.request :as yrq])
|
|
(:import
|
|
java.util.UUID
|
|
org.postgresql.ds.PGSimpleDataSource))
|
|
|
|
(def ^:dynamic *system* nil)
|
|
(def ^:dynamic *pool* nil)
|
|
|
|
(def defaults
|
|
{:database-uri "postgresql://postgres/penpot_test"
|
|
:redis-uri "redis://redis/1"})
|
|
|
|
(def config
|
|
(->> (cf/read-env "penpot-test")
|
|
(merge cf/defaults defaults)
|
|
(us/conform ::cf/config)))
|
|
|
|
(def default-flags
|
|
[:enable-secure-session-cookies
|
|
:enable-email-verification
|
|
:enable-smtp
|
|
:enable-quotes])
|
|
|
|
(defn state-init
|
|
[next]
|
|
(with-redefs [app.config/flags (flags/parse flags/default default-flags)
|
|
app.config/config config
|
|
app.loggers.audit/submit! (constantly nil)
|
|
app.auth/derive-password identity
|
|
app.auth/verify-password (fn [a b] {:valid (= a b)})]
|
|
|
|
(let [templates [{:id "test"
|
|
:name "test"
|
|
:file-uri "test"
|
|
:thumbnail-uri "test"
|
|
:path (-> "backend_tests/test_files/template.penpot" io/resource fs/path)}]
|
|
system (-> (merge main/system-config main/worker-config)
|
|
(assoc-in [:app.redis/redis :app.redis/uri] (:redis-uri config))
|
|
(assoc-in [:app.db/pool :uri] (:database-uri config))
|
|
(assoc-in [:app.db/pool :username] (:database-username config))
|
|
(assoc-in [:app.db/pool :password] (:database-password config))
|
|
(assoc-in [:app.rpc/methods :templates] templates)
|
|
(dissoc :app.srepl/server
|
|
:app.http/server
|
|
:app.http/router
|
|
:app.auth.oidc/google-provider
|
|
:app.auth.oidc/gitlab-provider
|
|
:app.auth.oidc/github-provider
|
|
:app.auth.oidc/generic-provider
|
|
:app.setup/builtin-templates
|
|
:app.auth.oidc/routes
|
|
:app.worker/executors-monitor
|
|
:app.http.oauth/handler
|
|
:app.notifications/handler
|
|
:app.loggers.mattermost/reporter
|
|
:app.loggers.loki/reporter
|
|
:app.loggers.database/reporter
|
|
:app.loggers.zmq/receiver
|
|
:app.worker/cron
|
|
:app.worker/worker))
|
|
_ (ig/load-namespaces system)
|
|
system (-> (ig/prep system)
|
|
(ig/init))]
|
|
(try
|
|
(binding [*system* system
|
|
*pool* (:app.db/pool system)]
|
|
(next))
|
|
(finally
|
|
(ig/halt! system))))))
|
|
|
|
(defn database-reset
|
|
[next]
|
|
(let [sql (str "SELECT table_name "
|
|
" FROM information_schema.tables "
|
|
" WHERE table_schema = 'public' "
|
|
" AND table_name != 'migrations';")]
|
|
(db/with-atomic [conn *pool*]
|
|
(let [result (->> (db/exec! conn [sql])
|
|
(map :table-name))]
|
|
(db/exec! conn [(str "TRUNCATE "
|
|
(apply str (interpose ", " result))
|
|
" CASCADE;")]))))
|
|
(next))
|
|
|
|
(defn clean-storage
|
|
[next]
|
|
(let [path (fs/path "/tmp/penpot")]
|
|
(when (fs/exists? path)
|
|
(fs/delete (fs/path "/tmp/penpot")))
|
|
(next)))
|
|
|
|
(defn serial
|
|
[& funcs]
|
|
(fn [next]
|
|
(loop [f (first funcs)
|
|
fs (rest funcs)]
|
|
(when f
|
|
(let [prm (promise)]
|
|
(f #(deliver prm true))
|
|
(deref prm)
|
|
(recur (first fs)
|
|
(rest fs)))))
|
|
(next)))
|
|
|
|
(defn mk-uuid
|
|
[prefix & args]
|
|
(UUID/nameUUIDFromBytes (-> (apply str prefix args)
|
|
(.getBytes "UTF-8"))))
|
|
;; --- FACTORIES
|
|
|
|
(defn create-profile*
|
|
([i] (create-profile* *pool* i {}))
|
|
([i params] (create-profile* *pool* i params))
|
|
([pool i params]
|
|
(let [params (merge {:id (mk-uuid "profile" i)
|
|
:fullname (str "Profile " i)
|
|
:email (str "profile" i ".test@nodomain.com")
|
|
:password "123123"
|
|
:is-demo false}
|
|
params)]
|
|
(with-open [conn (db/open pool)]
|
|
(->> params
|
|
(cmd.auth/create-profile! conn)
|
|
(cmd.auth/create-profile-rels! conn))))))
|
|
|
|
(defn create-project*
|
|
([i params] (create-project* *pool* i params))
|
|
([pool i {:keys [profile-id team-id] :as params}]
|
|
(us/assert uuid? profile-id)
|
|
(us/assert uuid? team-id)
|
|
(with-open [conn (db/open pool)]
|
|
(->> (merge {:id (mk-uuid "project" i)
|
|
:name (str "project" i)}
|
|
params)
|
|
(#'teams/create-project conn)))))
|
|
|
|
(defn create-file*
|
|
([i params]
|
|
(create-file* *pool* i params))
|
|
([pool i {:keys [profile-id project-id] :as params}]
|
|
(us/assert uuid? profile-id)
|
|
(us/assert uuid? project-id)
|
|
(with-open [conn (db/open pool)]
|
|
(files.create/create-file conn
|
|
(merge {:id (mk-uuid "file" i)
|
|
:name (str "file" i)
|
|
:components-v2 true}
|
|
params)))))
|
|
|
|
(defn mark-file-deleted*
|
|
([params] (mark-file-deleted* *pool* params))
|
|
([conn {:keys [id] :as params}]
|
|
(#'files/mark-file-deleted conn {:id id})))
|
|
|
|
(defn create-team*
|
|
([i params] (create-team* *pool* i params))
|
|
([pool i {:keys [profile-id] :as params}]
|
|
(us/assert uuid? profile-id)
|
|
(with-open [conn (db/open pool)]
|
|
(let [id (mk-uuid "team" i)]
|
|
(teams/create-team conn {:id id
|
|
:profile-id profile-id
|
|
:name (str "team" i)})))))
|
|
|
|
(defn create-file-media-object*
|
|
([params] (create-file-media-object* *pool* params))
|
|
([pool {:keys [name width height mtype file-id is-local media-id]
|
|
:or {name "sample" width 100 height 100 mtype "image/svg+xml" is-local true}}]
|
|
|
|
(with-open [conn (db/open pool)]
|
|
(db/insert! conn :file-media-object
|
|
{:id (uuid/next)
|
|
:file-id file-id
|
|
:is-local is-local
|
|
:name name
|
|
:media-id media-id
|
|
:width width
|
|
:height height
|
|
:mtype mtype}))))
|
|
|
|
(defn link-file-to-library*
|
|
([params] (link-file-to-library* *pool* params))
|
|
([pool {:keys [file-id library-id] :as params}]
|
|
(with-open [conn (db/open pool)]
|
|
(#'files/link-file-to-library conn {:file-id file-id :library-id library-id}))))
|
|
|
|
(defn create-complaint-for
|
|
[pool {:keys [id created-at type]}]
|
|
(with-open [conn (db/open pool)]
|
|
(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
|
|
[pool {:keys [email type created-at]}]
|
|
(with-open [conn (db/open pool)]
|
|
(db/insert! conn :global-complaint-report
|
|
{:email email
|
|
:type (name type)
|
|
:created-at (or created-at (dt/now))
|
|
:content (db/tjson {})})))
|
|
|
|
(defn create-team-role*
|
|
([params] (create-team-role* *pool* params))
|
|
([pool {:keys [team-id profile-id role] :or {role :owner}}]
|
|
(with-open [conn (db/open pool)]
|
|
(#'teams/create-team-role conn {:team-id team-id
|
|
:profile-id profile-id
|
|
:role role}))))
|
|
|
|
(defn create-project-role*
|
|
([params] (create-project-role* *pool* params))
|
|
([pool {:keys [project-id profile-id role] :or {role :owner}}]
|
|
(with-open [conn (db/open pool)]
|
|
(#'teams/create-project-role conn {:project-id project-id
|
|
:profile-id profile-id
|
|
:role role}))))
|
|
|
|
(defn create-file-role*
|
|
([params] (create-file-role* *pool* params))
|
|
([pool {:keys [file-id profile-id role] :or {role :owner}}]
|
|
(with-open [conn (db/open pool)]
|
|
(files.create/create-file-role! conn {:file-id file-id
|
|
:profile-id profile-id
|
|
:role role}))))
|
|
|
|
(defn update-file*
|
|
([params] (update-file* *pool* params))
|
|
([pool {:keys [file-id changes session-id profile-id revn]
|
|
:or {session-id (uuid/next) revn 0}}]
|
|
(with-open [conn (db/open pool)]
|
|
(let [features #{"components/v2"}
|
|
cfg (-> (select-keys *system* [::mbus/msgbus ::mtx/metrics])
|
|
(assoc :conn conn))]
|
|
(files.update/update-file cfg
|
|
{:id file-id
|
|
:revn revn
|
|
:features features
|
|
:changes changes
|
|
:session-id session-id
|
|
:profile-id profile-id})))))
|
|
|
|
(defn create-webhook*
|
|
([params] (create-webhook* *pool* params))
|
|
([pool {:keys [team-id id uri mtype is-active]
|
|
:or {is-active true
|
|
mtype "application/json"
|
|
uri "http://example.com/webhook"}}]
|
|
(db/insert! pool :webhook
|
|
{:id (or id (uuid/next))
|
|
:team-id team-id
|
|
:uri uri
|
|
:is-active is-active
|
|
:mtype mtype})))
|
|
|
|
;; --- RPC HELPERS
|
|
|
|
(defn handle-error
|
|
[^Throwable err]
|
|
(if (instance? java.util.concurrent.ExecutionException err)
|
|
(handle-error (.getCause err))
|
|
err))
|
|
|
|
(defmacro try-on!
|
|
[expr]
|
|
`(try
|
|
(let [result# (deref ~expr)
|
|
result# (cond-> result# (rph/wrapped? result#) deref)]
|
|
{:error nil
|
|
:result result#})
|
|
(catch Exception e#
|
|
{:error (handle-error e#)
|
|
:result nil})))
|
|
|
|
(defn command!
|
|
[{:keys [::type] :as data}]
|
|
(let [method-fn (get-in *system* [:app.rpc/methods :commands type])]
|
|
;; (app.common.pprint/pprint (:app.rpc/methods *system*))
|
|
(try-on! (method-fn (-> data
|
|
(dissoc ::type)
|
|
(assoc :app.rpc/request-at (dt/now)))))))
|
|
|
|
(defn mutation!
|
|
[{:keys [::type profile-id] :as data}]
|
|
(let [method-fn (get-in *system* [:app.rpc/methods :mutations type])]
|
|
(try-on! (method-fn (-> data
|
|
(dissoc ::type)
|
|
(assoc ::rpc/profile-id profile-id)
|
|
(d/without-nils))))))
|
|
|
|
(defn query!
|
|
[{:keys [::type profile-id] :as data}]
|
|
(let [method-fn (get-in *system* [:app.rpc/methods :queries type])]
|
|
(try-on! (method-fn (-> data
|
|
(dissoc ::type)
|
|
(assoc ::rpc/profile-id profile-id)
|
|
(d/without-nils))))))
|
|
|
|
|
|
(defn run-task!
|
|
([name]
|
|
(run-task! name {}))
|
|
([name params]
|
|
(let [tasks (:app.worker/registry *system*)]
|
|
(let [task-fn (get tasks (d/name name))]
|
|
(task-fn params)))))
|
|
|
|
;; --- UTILS
|
|
|
|
(defn print-error!
|
|
[error]
|
|
(let [data (ex-data error)]
|
|
(cond
|
|
(= :spec-validation (:code data))
|
|
(println
|
|
(us/pretty-explain data))
|
|
|
|
(= :service-error (:type data))
|
|
(print-error! (.getCause ^Throwable error))
|
|
|
|
:else
|
|
(.printStackTrace ^Throwable error))))
|
|
|
|
(defn print-result!
|
|
[{:keys [error result]}]
|
|
(if error
|
|
(do
|
|
(println "====> START ERROR")
|
|
(print-error! error)
|
|
(println "====> END ERROR"))
|
|
(do
|
|
(println "====> START RESPONSE")
|
|
(pp/pprint result)
|
|
(println "====> END RESPONSE"))))
|
|
|
|
(defn exception?
|
|
[v]
|
|
(instance? Throwable v))
|
|
|
|
(defn ex-info?
|
|
[v]
|
|
(instance? clojure.lang.ExceptionInfo v))
|
|
|
|
(defn ex-type
|
|
[e]
|
|
(:type (ex-data e)))
|
|
|
|
(defn ex-code
|
|
[e]
|
|
(:code (ex-data e)))
|
|
|
|
(defn ex-of-type?
|
|
[e type]
|
|
(let [data (ex-data e)]
|
|
(= type (:type data))))
|
|
|
|
(defn ex-of-code?
|
|
[e code]
|
|
(let [data (ex-data e)]
|
|
(= code (:code data))))
|
|
|
|
(defn ex-with-code?
|
|
[e code]
|
|
(let [data (ex-data e)]
|
|
(= code (:code data))))
|
|
|
|
(defn success?
|
|
[{:keys [result error]}]
|
|
(nil? error))
|
|
|
|
(defn tempfile
|
|
[source]
|
|
(let [rsc (io/resource source)
|
|
tmp (fs/create-tempfile)]
|
|
(io/copy (io/file rsc)
|
|
(io/file tmp))
|
|
tmp))
|
|
|
|
(defn pause
|
|
[]
|
|
(let [^java.io.Console cnsl (System/console)]
|
|
(println "[waiting RETURN]")
|
|
(.readLine cnsl)
|
|
nil))
|
|
|
|
(defn db-exec!
|
|
[sql]
|
|
(db/exec! *pool* sql))
|
|
|
|
(defn db-insert!
|
|
[& params]
|
|
(apply db/insert! *pool* params))
|
|
|
|
(defn db-query
|
|
[& params]
|
|
(apply db/query *pool* params))
|
|
|
|
(defn db-get
|
|
[& params]
|
|
(apply db/get* *pool* params))
|
|
|
|
(defn sleep
|
|
[ms-or-duration]
|
|
(Thread/sleep (inst-ms (dt/duration ms-or-duration))))
|
|
|
|
(defn config-get-mock
|
|
[data]
|
|
(fn
|
|
([key]
|
|
(get data key (get cf/config key)))
|
|
([key default]
|
|
(get data key (get cf/config key default)))))
|
|
|
|
(defn reset-mock!
|
|
[m]
|
|
(swap! m (fn [m]
|
|
(-> m
|
|
(assoc :called? false)
|
|
(assoc :call-count 0)
|
|
(assoc :return-list [])
|
|
(assoc :call-args nil)
|
|
(assoc :call-args-list [])))))
|