mirror of
https://github.com/penpot/penpot.git
synced 2025-03-25 06:01:46 -05:00
♻️ Reimplement GC mechanism for penpot database objects.
This commit is contained in:
parent
71c4145ea2
commit
860e0227af
20 changed files with 437 additions and 104 deletions
|
@ -172,18 +172,21 @@
|
|||
{:cron #app/cron "0 0 * * * ?" ;; hourly
|
||||
:task :file-xlog-gc}
|
||||
|
||||
{:cron #app/cron "0 0 1 * * ?" ;; daily (1 hour shift)
|
||||
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
||||
:task :storage-deleted-gc}
|
||||
|
||||
{:cron #app/cron "0 0 2 * * ?" ;; daily (2 hour shift)
|
||||
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
||||
:task :storage-touched-gc}
|
||||
|
||||
{:cron #app/cron "0 0 3 * * ?" ;; daily (3 hour shift)
|
||||
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
||||
:task :session-gc}
|
||||
|
||||
{:cron #app/cron "0 0 * * * ?" ;; hourly
|
||||
:task :storage-recheck}
|
||||
|
||||
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
||||
:task :objects-gc}
|
||||
|
||||
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
||||
:task :tasks-gc}
|
||||
|
||||
|
@ -203,6 +206,7 @@
|
|||
{:metrics (ig/ref :app.metrics/metrics)
|
||||
:tasks
|
||||
{:sendmail (ig/ref :app.emails/sendmail-handler)
|
||||
:objects-gc (ig/ref :app.tasks.objects-gc/handler)
|
||||
:delete-object (ig/ref :app.tasks.delete-object/handler)
|
||||
:delete-profile (ig/ref :app.tasks.delete-profile/handler)
|
||||
:file-media-gc (ig/ref :app.tasks.file-media-gc/handler)
|
||||
|
@ -236,6 +240,11 @@
|
|||
{:pool (ig/ref :app.db/pool)
|
||||
:storage (ig/ref :app.storage/storage)}
|
||||
|
||||
:app.tasks.objects-gc/handler
|
||||
{:pool (ig/ref :app.db/pool)
|
||||
:storage (ig/ref :app.storage/storage)
|
||||
:max-age cf/deletion-delay}
|
||||
|
||||
:app.tasks.delete-profile/handler
|
||||
{:pool (ig/ref :app.db/pool)}
|
||||
|
||||
|
|
|
@ -175,6 +175,15 @@
|
|||
|
||||
{:name "0055-mod-file-media-object-table"
|
||||
:fn (mg/resource "app/migrations/sql/0055-mod-file-media-object-table.sql")}
|
||||
|
||||
{:name "0056-add-missing-index-on-deleted-at"
|
||||
:fn (mg/resource "app/migrations/sql/0056-add-missing-index-on-deleted-at.sql")}
|
||||
|
||||
{:name "0057-del-profile-on-delete-trigger"
|
||||
:fn (mg/resource "app/migrations/sql/0057-del-profile-on-delete-trigger.sql")}
|
||||
|
||||
{:name "0058-del-team-on-delete-trigger"
|
||||
:fn (mg/resource "app/migrations/sql/0058-del-team-on-delete-trigger.sql")}
|
||||
])
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
CREATE INDEX profile_deleted_at_idx
|
||||
ON profile(deleted_at, id)
|
||||
WHERE deleted_at IS NOT NULL;
|
||||
|
||||
CREATE INDEX project_deleted_at_idx
|
||||
ON project(deleted_at, id)
|
||||
WHERE deleted_at IS NOT NULL;
|
||||
|
||||
CREATE INDEX team_deleted_at_idx
|
||||
ON team(deleted_at, id)
|
||||
WHERE deleted_at IS NOT NULL;
|
||||
|
||||
CREATE INDEX team_font_variant_deleted_at_idx
|
||||
ON team_font_variant(deleted_at, id)
|
||||
WHERE deleted_at IS NOT NULL;
|
|
@ -0,0 +1,2 @@
|
|||
DROP TRIGGER profile__on_delete__tgr ON profile CASCADE;
|
||||
DROP FUNCTION on_delete_profile ();
|
|
@ -0,0 +1,2 @@
|
|||
DROP TRIGGER team__on_delete__tgr ON team CASCADE;
|
||||
DROP FUNCTION on_delete_team ();
|
|
@ -15,7 +15,7 @@
|
|||
[app.rpc.mutations.profile :as profile]
|
||||
[app.setup.initial-data :as sid]
|
||||
[app.util.services :as sv]
|
||||
[app.worker :as wrk]
|
||||
[app.util.time :as dt]
|
||||
[buddy.core.codecs :as bc]
|
||||
[buddy.core.nonce :as bn]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
@ -35,6 +35,7 @@
|
|||
:email email
|
||||
:fullname fullname
|
||||
:is-demo true
|
||||
:deleted-at (dt/in-future cfg/deletion-delay)
|
||||
:password password
|
||||
:props {:onboarding-viewed true}}]
|
||||
|
||||
|
@ -48,12 +49,6 @@
|
|||
(#'profile/create-profile-relations conn)
|
||||
(sid/load-initial-project! conn))
|
||||
|
||||
;; Schedule deletion of the demo profile
|
||||
(wrk/submit! {::wrk/task :delete-profile
|
||||
::wrk/delay cfg/deletion-delay
|
||||
::wrk/conn conn
|
||||
:profile-id id})
|
||||
|
||||
(with-meta {:email email
|
||||
:password password}
|
||||
{::audit/profile-id id}))))
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
[app.common.pages.migrations :as pmg]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cfg]
|
||||
[app.db :as db]
|
||||
[app.rpc.permissions :as perms]
|
||||
[app.rpc.queries.files :as files]
|
||||
|
@ -19,7 +18,6 @@
|
|||
[app.util.blob :as blob]
|
||||
[app.util.services :as sv]
|
||||
[app.util.time :as dt]
|
||||
[app.worker :as wrk]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
@ -121,14 +119,6 @@
|
|||
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(files/check-edition-permissions! conn profile-id id)
|
||||
|
||||
;; Schedule object deletion
|
||||
(wrk/submit! {::wrk/task :delete-object
|
||||
::wrk/delay cfg/deletion-delay
|
||||
::wrk/conn conn
|
||||
:id id
|
||||
:type :file})
|
||||
|
||||
(mark-file-deleted conn params)))
|
||||
|
||||
(defn mark-file-deleted
|
||||
|
|
|
@ -104,21 +104,10 @@
|
|||
(db/with-atomic [conn pool]
|
||||
(teams/check-edition-permissions! conn profile-id team-id)
|
||||
|
||||
(let [items (db/query conn :team-font-variant
|
||||
{:font-id id :team-id team-id}
|
||||
{:for-update true})]
|
||||
(doseq [item items]
|
||||
;; Schedule object deletion
|
||||
(wrk/submit! {::wrk/task :delete-object
|
||||
::wrk/delay cf/deletion-delay
|
||||
::wrk/conn conn
|
||||
:id (:id item)
|
||||
:type :team-font-variant}))
|
||||
|
||||
(db/update! conn :team-font-variant
|
||||
{:deleted-at (dt/now)}
|
||||
{:font-id id :team-id team-id})
|
||||
nil)))
|
||||
(db/update! conn :team-font-variant
|
||||
{:deleted-at (dt/now)}
|
||||
{:font-id id :team-id team-id})
|
||||
nil))
|
||||
|
||||
;; --- DELETE FONT VARIANT
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
[app.storage :as sto]
|
||||
[app.util.services :as sv]
|
||||
[app.util.time :as dt]
|
||||
[app.worker :as wrk]
|
||||
[buddy.hashers :as hashers]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]))
|
||||
|
@ -179,9 +178,9 @@
|
|||
:valid false})))
|
||||
|
||||
(defn create-profile
|
||||
"Create the profile entry on the database with limited input
|
||||
filling all the other fields with defaults."
|
||||
[conn {:keys [id fullname email password is-active is-muted is-demo opts]
|
||||
"Create the profile entry on the database with limited input filling
|
||||
all the other fields with defaults."
|
||||
[conn {:keys [id fullname email password is-active is-muted is-demo opts deleted-at]
|
||||
:or {is-active false is-muted false is-demo false}
|
||||
:as params}]
|
||||
(let [id (or id (uuid/next))
|
||||
|
@ -193,6 +192,7 @@
|
|||
:email (str/lower email)
|
||||
:auth-backend "penpot"
|
||||
:password password
|
||||
:deleted-at deleted-at
|
||||
:props props
|
||||
:is-active is-active
|
||||
:is-muted is-muted
|
||||
|
@ -264,7 +264,8 @@
|
|||
(let [profile (->> (profile/retrieve-profile-data-by-email conn email)
|
||||
(validate-profile)
|
||||
(profile/strip-private-attrs)
|
||||
(profile/populate-additional-data conn))]
|
||||
(profile/populate-additional-data conn))
|
||||
profile (update profile :props db/decode-transit-pgobject)]
|
||||
(if-let [token (:invitation-token params)]
|
||||
;; If the request comes with an invitation token, this means
|
||||
;; that user wants to accept it with different user. A very
|
||||
|
@ -619,12 +620,6 @@
|
|||
(db/with-atomic [conn pool]
|
||||
(check-can-delete-profile! conn profile-id)
|
||||
|
||||
;; Schedule a complete deletion of profile
|
||||
(wrk/submit! {::wrk/task :delete-profile
|
||||
::wrk/delay cfg/deletion-delay
|
||||
::wrk/conn conn
|
||||
:profile-id profile-id})
|
||||
|
||||
(db/update! conn :profile
|
||||
{:deleted-at (dt/now)}
|
||||
{:id profile-id})
|
||||
|
|
|
@ -8,14 +8,12 @@
|
|||
(:require
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cfg]
|
||||
[app.db :as db]
|
||||
[app.rpc.permissions :as perms]
|
||||
[app.rpc.queries.projects :as proj]
|
||||
[app.rpc.queries.teams :as teams]
|
||||
[app.util.services :as sv]
|
||||
[app.util.time :as dt]
|
||||
[app.worker :as wrk]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
@ -123,14 +121,6 @@
|
|||
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(proj/check-edition-permissions! conn profile-id id)
|
||||
|
||||
;; Schedule object deletion
|
||||
(wrk/submit! {::wrk/task :delete-object
|
||||
::wrk/delay cfg/deletion-delay
|
||||
::wrk/conn conn
|
||||
:id id
|
||||
:type :project})
|
||||
|
||||
(db/update! conn :project
|
||||
{:deleted-at (dt/now)}
|
||||
{:id id})
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
[app.common.exceptions :as ex]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cfg]
|
||||
[app.db :as db]
|
||||
[app.emails :as eml]
|
||||
[app.media :as media]
|
||||
|
@ -21,7 +20,6 @@
|
|||
[app.storage :as sto]
|
||||
[app.util.services :as sv]
|
||||
[app.util.time :as dt]
|
||||
[app.worker :as wrk]
|
||||
[clojure.spec.alpha :as s]
|
||||
[datoteka.core :as fs]))
|
||||
|
||||
|
@ -135,13 +133,6 @@
|
|||
(ex/raise :type :validation
|
||||
:code :only-owner-can-delete-team))
|
||||
|
||||
;; Schedule object deletion
|
||||
(wrk/submit! {::wrk/task :delete-object
|
||||
::wrk/delay cfg/deletion-delay
|
||||
::wrk/conn conn
|
||||
:id id
|
||||
:type :team})
|
||||
|
||||
(db/update! conn :team
|
||||
{:deleted-at (dt/now)}
|
||||
{:id id})
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
[app.common.uuid :as uuid]
|
||||
[app.db :as db]
|
||||
[app.util.services :as sv]
|
||||
[clojure.spec.alpha :as s]))
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
||||
|
@ -90,16 +91,11 @@
|
|||
|
||||
profile))
|
||||
|
||||
(def sql:retrieve-profile-by-email
|
||||
"select p.* from profile as p
|
||||
where p.email = lower(?)
|
||||
and p.deleted_at is null")
|
||||
|
||||
(defn retrieve-profile-data-by-email
|
||||
[conn email]
|
||||
(let [sql [sql:retrieve-profile-by-email email]]
|
||||
(some-> (db/exec-one! conn sql)
|
||||
(decode-profile-row))))
|
||||
(try
|
||||
(db/get-by-params conn :profile {:email (str/lower email)})
|
||||
(catch Exception _e)))
|
||||
|
||||
;; --- Attrs Helpers
|
||||
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
;; TODO: DEPRECATED
|
||||
;; Should be removed in the 1.8.x
|
||||
|
||||
(ns app.tasks.delete-object
|
||||
"Generic task for permanent deletion of objects."
|
||||
(:require
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
[clojure.spec.alpha :as s]
|
||||
[integrant.core :as ig]))
|
||||
|
||||
;; TODO: DEPRECATED
|
||||
;; Should be removed in the 1.8.x
|
||||
|
||||
(declare delete-profile-data)
|
||||
|
||||
;; --- INIT
|
||||
|
|
|
@ -100,6 +100,7 @@
|
|||
:id (:id mobj)
|
||||
:media-id (:media-id mobj)
|
||||
:thumbnail-id (:thumbnail-id mobj))
|
||||
|
||||
;; NOTE: deleting the file-media-object in the database
|
||||
;; automatically marks as toched the referenced storage
|
||||
;; objects. The touch mechanism is needed because many files can
|
||||
|
|
152
backend/src/app/tasks/objects_gc.clj
Normal file
152
backend/src/app/tasks/objects_gc.clj
Normal file
|
@ -0,0 +1,152 @@
|
|||
;; 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) UXBOX Labs SL
|
||||
|
||||
(ns app.tasks.objects-gc
|
||||
"A maintenance task that performs a general purpose garbage collection
|
||||
of deleted objects."
|
||||
(:require
|
||||
[app.db :as db]
|
||||
[app.storage :as sto]
|
||||
[app.util.logging :as l]
|
||||
[app.util.time :as dt]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[integrant.core :as ig]))
|
||||
|
||||
(def target-tables
|
||||
["profile"
|
||||
"team"
|
||||
"file"
|
||||
"project"
|
||||
"team_font_variant"])
|
||||
|
||||
(defmulti delete-objects :table)
|
||||
|
||||
(def sql:delete-objects
|
||||
"with deleted as (
|
||||
select id from %(table)s
|
||||
where deleted_at is not null
|
||||
and deleted_at < now() - ?::interval
|
||||
order by deleted_at
|
||||
limit %(limit)s
|
||||
)
|
||||
delete from %(table)s
|
||||
where id in (select id from deleted)
|
||||
returning *")
|
||||
|
||||
;; --- IMPL: generic object deletion
|
||||
|
||||
(defmethod delete-objects :default
|
||||
[{:keys [conn max-age table] :as cfg}]
|
||||
(let [sql (str/fmt sql:delete-objects
|
||||
{:table table :limit 50})
|
||||
result (db/exec! conn [sql max-age])]
|
||||
|
||||
(doseq [{:keys [id] :as item} result]
|
||||
(l/trace :action "delete object" :table table :id id))
|
||||
|
||||
(count result)))
|
||||
|
||||
;; --- IMPL: team-font-variant deletion
|
||||
|
||||
(defmethod delete-objects "team_font_variant"
|
||||
[{:keys [conn max-age storage table] :as cfg}]
|
||||
(let [sql (str/fmt sql:delete-objects
|
||||
{:table table :limit 50})
|
||||
fonts (db/exec! conn [sql max-age])
|
||||
storage (assoc storage :conn conn)]
|
||||
(doseq [{:keys [id] :as font} fonts]
|
||||
(l/trace :action "delete object" :table table :id id)
|
||||
(some->> (:woff1-file-id font) (sto/del-object storage))
|
||||
(some->> (:woff2-file-id font) (sto/del-object storage))
|
||||
(some->> (:otf-file-id font) (sto/del-object storage))
|
||||
(some->> (:ttf-file-id font) (sto/del-object storage)))
|
||||
(count fonts)))
|
||||
|
||||
;; --- IMPL: team deletion
|
||||
|
||||
(defmethod delete-objects "team"
|
||||
[{:keys [conn max-age storage table] :as cfg}]
|
||||
(let [sql (str/fmt sql:delete-objects
|
||||
{:table table :limit 50})
|
||||
teams (db/exec! conn [sql max-age])
|
||||
storage (assoc storage :conn conn)]
|
||||
|
||||
(doseq [{:keys [id] :as team} teams]
|
||||
(l/trace :action "delete object" :table table :id id)
|
||||
(some->> (:photo-id team) (sto/del-object storage)))
|
||||
|
||||
(count teams)))
|
||||
|
||||
;; --- IMPL: profile deletion
|
||||
|
||||
(def sql:retrieve-deleted-profiles
|
||||
"select id, photo_id from profile
|
||||
where deleted_at is not null
|
||||
and deleted_at < now() - ?::interval
|
||||
order by deleted_at
|
||||
limit %(limit)s
|
||||
for update")
|
||||
|
||||
(def sql:mark-owned-teams-deleted
|
||||
"with owned as (
|
||||
select tpr.team_id as id
|
||||
from team_profile_rel as tpr
|
||||
where tpr.is_owner is true
|
||||
and tpr.profile_id = ?
|
||||
)
|
||||
update team set deleted_at = now() - ?::interval
|
||||
where id in (select id from owned)")
|
||||
|
||||
(defmethod delete-objects "profile"
|
||||
[{:keys [conn max-age storage table] :as cfg}]
|
||||
(let [sql (str/fmt sql:retrieve-deleted-profiles {:limit 50})
|
||||
profiles (db/exec! conn [sql max-age])
|
||||
storage (assoc storage :conn conn)]
|
||||
|
||||
(doseq [{:keys [id] :as profile} profiles]
|
||||
(l/trace :action "delete object" :table table :id id)
|
||||
|
||||
;; Mark the owned teams as deleted; this enables them to be procesed
|
||||
;; in the same transaction in the "team" table step.
|
||||
(db/exec-one! conn [sql:mark-owned-teams-deleted id max-age])
|
||||
|
||||
;; Mark as deleted the storage object related with the photo-id
|
||||
;; field.
|
||||
(some->> (:photo-id profile) (sto/del-object storage))
|
||||
|
||||
;; And finally, permanently delete the profile.
|
||||
(db/delete! conn :profile {:id id}))
|
||||
|
||||
(count profiles)))
|
||||
|
||||
;; --- INIT
|
||||
|
||||
(defn- process-table
|
||||
[{:keys [table] :as cfg}]
|
||||
(loop [n 0]
|
||||
(let [res (delete-objects cfg)]
|
||||
(if (pos? res)
|
||||
(recur (+ n res))
|
||||
(l/debug :hint "table gc summary" :table table :deleted n)))))
|
||||
|
||||
(s/def ::max-age ::dt/duration)
|
||||
|
||||
(defmethod ig/pre-init-spec ::handler [_]
|
||||
(s/keys :req-un [::db/pool ::sto/storage ::max-age]))
|
||||
|
||||
(defmethod ig/init-key ::handler
|
||||
[_ {:keys [pool max-age] :as cfg}]
|
||||
(fn [task]
|
||||
;; Checking first on task argument allows properly testing it.
|
||||
(let [max-age (get task :max-age max-age)]
|
||||
(db/with-atomic [conn pool]
|
||||
(let [max-age (db/interval max-age)
|
||||
cfg (-> cfg
|
||||
(assoc :max-age max-age)
|
||||
(assoc :conn conn))]
|
||||
(doseq [table target-tables]
|
||||
(process-table (assoc cfg :table table))))))))
|
|
@ -11,6 +11,7 @@
|
|||
[app.http :as http]
|
||||
[app.storage :as sto]
|
||||
[app.test-helpers :as th]
|
||||
[app.util.time :as dt]
|
||||
[clojure.test :as t]
|
||||
[datoteka.core :as fs]))
|
||||
|
||||
|
@ -337,3 +338,69 @@
|
|||
(t/is (th/ex-info? error))
|
||||
(t/is (th/ex-of-type? error :not-found))))
|
||||
|
||||
(t/deftest deletion-test
|
||||
(let [task (:app.tasks.objects-gc/handler th/*system*)
|
||||
profile1 (th/create-profile* 1)
|
||||
file (th/create-file* 1 {:project-id (:default-project-id profile1)
|
||||
:profile-id (:id profile1)})]
|
||||
;; file is not deleted because it does not meet all
|
||||
;; conditions to be deleted.
|
||||
(let [result (task {:max-age (dt/duration 0)})]
|
||||
(t/is (nil? result)))
|
||||
|
||||
;; query the list of files
|
||||
(let [data {::th/type :project-files
|
||||
:project-id (:default-project-id profile1)
|
||||
:profile-id (:id profile1)}
|
||||
out (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (= 1 (count result)))))
|
||||
|
||||
;; Request file to be deleted
|
||||
(let [params {::th/type :delete-file
|
||||
:id (:id file)
|
||||
:profile-id (:id profile1)}
|
||||
out (th/mutation! params)]
|
||||
(t/is (nil? (:error out))))
|
||||
|
||||
;; query the list of files after soft deletion
|
||||
(let [data {::th/type :project-files
|
||||
:project-id (:default-project-id profile1)
|
||||
:profile-id (:id profile1)}
|
||||
out (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (= 0 (count result)))))
|
||||
|
||||
;; run permanent deletion (should be noop)
|
||||
(let [result (task {:max-age (dt/duration {:minutes 1})})]
|
||||
(t/is (nil? result)))
|
||||
|
||||
;; query the list of file libraries of a after hard deletion
|
||||
(let [data {::th/type :file-libraries
|
||||
:file-id (:id file)
|
||||
:profile-id (:id profile1)}
|
||||
out (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (= 0 (count result)))))
|
||||
|
||||
;; run permanent deletion
|
||||
(let [result (task {:max-age (dt/duration 0)})]
|
||||
(t/is (nil? result)))
|
||||
|
||||
;; query the list of file libraries of a after hard deletion
|
||||
(let [data {::th/type :file-libraries
|
||||
:file-id (:id file)
|
||||
:profile-id (:id profile1)}
|
||||
out (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(let [error (:error out)
|
||||
error-data (ex-data error)]
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (= (:type error-data) :not-found))))
|
||||
))
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[app.db :as db]
|
||||
[app.rpc.mutations.profile :as profile]
|
||||
[app.test-helpers :as th]
|
||||
[app.util.time :as dt]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.test :as t]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -117,7 +118,7 @@
|
|||
))
|
||||
|
||||
(t/deftest profile-deletion-simple
|
||||
(let [task (:app.tasks.delete-profile/handler th/*system*)
|
||||
(let [task (:app.tasks.objects-gc/handler th/*system*)
|
||||
prof (th/create-profile* 1)
|
||||
file (th/create-file* 1 {:profile-id (:id prof)
|
||||
:project-id (:default-project-id prof)
|
||||
|
@ -125,23 +126,14 @@
|
|||
|
||||
;; profile is not deleted because it does not meet all
|
||||
;; conditions to be deleted.
|
||||
(let [result (task {:props {:profile-id (:id prof)}})]
|
||||
(let [result (task {:max-age (dt/duration 0)})]
|
||||
(t/is (nil? result)))
|
||||
|
||||
;; Request profile to be deleted
|
||||
(with-mocks [mock {:target 'app.worker/submit! :return nil}]
|
||||
(let [params {::th/type :delete-profile
|
||||
:profile-id (:id prof)}
|
||||
out (th/mutation! params)]
|
||||
(t/is (nil? (:error out)))
|
||||
|
||||
;; check the mock
|
||||
(let [mock (deref mock)
|
||||
mock-params (first (:call-args mock))]
|
||||
(t/is (:called? mock))
|
||||
(t/is (= 1 (:call-count mock)))
|
||||
(t/is (= :delete-profile (:app.worker/task mock-params)))
|
||||
(t/is (= (:id prof) (:profile-id mock-params))))))
|
||||
(let [params {::th/type :delete-profile
|
||||
:profile-id (:id prof)}
|
||||
out (th/mutation! params)]
|
||||
(t/is (nil? (:error out))))
|
||||
|
||||
;; query files after profile soft deletion
|
||||
(let [params {::th/type :files
|
||||
|
@ -153,8 +145,8 @@
|
|||
(t/is (= 1 (count (:result out)))))
|
||||
|
||||
;; execute permanent deletion task
|
||||
(let [result (task {:props {:profile-id (:id prof)}})]
|
||||
(t/is (true? result)))
|
||||
(let [result (task {:max-age (dt/duration "-1m")})]
|
||||
(t/is (nil? result)))
|
||||
|
||||
;; query profile after delete
|
||||
(let [params {::th/type :profile
|
||||
|
@ -165,17 +157,6 @@
|
|||
error-data (ex-data error)]
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (= (:type error-data) :not-found))))
|
||||
|
||||
;; query files after profile soft deletion
|
||||
(let [params {::th/type :files
|
||||
:project-id (:default-project-id prof)
|
||||
:profile-id (:id prof)}
|
||||
out (th/query! params)]
|
||||
;; (th/print-result! out)
|
||||
(let [error (:error out)
|
||||
error-data (ex-data error)]
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (= (:type error-data) :not-found))))
|
||||
))
|
||||
|
||||
(t/deftest registration-domain-whitelist
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
[app.db :as db]
|
||||
[app.http :as http]
|
||||
[app.test-helpers :as th]
|
||||
[clojure.test :as t]
|
||||
[promesa.core :as p]))
|
||||
[app.util.time :as dt]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :once th/state-init)
|
||||
(t/use-fixtures :each th/database-reset)
|
||||
|
@ -170,3 +170,71 @@
|
|||
(t/is (th/ex-info? error))
|
||||
(t/is (th/ex-of-type? error :not-found))))
|
||||
|
||||
|
||||
(t/deftest test-deletion
|
||||
(let [task (:app.tasks.objects-gc/handler th/*system*)
|
||||
profile1 (th/create-profile* 1)
|
||||
project (th/create-project* 1 {:team-id (:default-team-id profile1)
|
||||
:profile-id (:id profile1)})]
|
||||
|
||||
;; project is not deleted because it does not meet all
|
||||
;; conditions to be deleted.
|
||||
(let [result (task {:max-age (dt/duration 0)})]
|
||||
(t/is (nil? result)))
|
||||
|
||||
;; query the list of projects
|
||||
(let [data {::th/type :projects
|
||||
:team-id (:default-team-id profile1)
|
||||
:profile-id (:id profile1)}
|
||||
out (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (= 2 (count result)))))
|
||||
|
||||
;; Request project to be deleted
|
||||
(let [params {::th/type :delete-project
|
||||
:id (:id project)
|
||||
:profile-id (:id profile1)}
|
||||
out (th/mutation! params)]
|
||||
(t/is (nil? (:error out))))
|
||||
|
||||
;; query the list of projects after soft deletion
|
||||
(let [data {::th/type :projects
|
||||
:team-id (:default-team-id profile1)
|
||||
:profile-id (:id profile1)}
|
||||
out (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (= 1 (count result)))))
|
||||
|
||||
;; run permanent deletion (should be noop)
|
||||
(let [result (task {:max-age (dt/duration {:minutes 1})})]
|
||||
(t/is (nil? result)))
|
||||
|
||||
;; query the list of files of a after soft deletion
|
||||
(let [data {::th/type :project-files
|
||||
:project-id (:id project)
|
||||
:profile-id (:id profile1)}
|
||||
out (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (= 0 (count result)))))
|
||||
|
||||
;; run permanent deletion
|
||||
(let [result (task {:max-age (dt/duration 0)})]
|
||||
(t/is (nil? result)))
|
||||
|
||||
;; query the list of files of a after hard deletion
|
||||
(let [data {::th/type :project-files
|
||||
:project-id (:id project)
|
||||
:profile-id (:id profile1)}
|
||||
out (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(let [error (:error out)
|
||||
error-data (ex-data error)]
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (= (:type error-data) :not-found))))
|
||||
))
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
[app.http :as http]
|
||||
[app.storage :as sto]
|
||||
[app.test-helpers :as th]
|
||||
[app.util.time :as dt]
|
||||
[clojure.test :as t]
|
||||
[datoteka.core :as fs]
|
||||
[mockery.core :refer [with-mocks]]))
|
||||
|
@ -80,6 +81,80 @@
|
|||
|
||||
)))
|
||||
|
||||
(t/deftest test-deletion
|
||||
(let [task (:app.tasks.objects-gc/handler th/*system*)
|
||||
profile1 (th/create-profile* 1 {:is-active true})
|
||||
team (th/create-team* 1 {:profile-id (:id profile1)})
|
||||
pool (:app.db/pool th/*system*)
|
||||
data {::th/type :delete-team
|
||||
:team-id (:id team)
|
||||
:profile-id (:id profile1)}]
|
||||
|
||||
;; team is not deleted because it does not meet all
|
||||
;; conditions to be deleted.
|
||||
(let [result (task {:max-age (dt/duration 0)})]
|
||||
(t/is (nil? result)))
|
||||
|
||||
;; query the list of teams
|
||||
(let [data {::th/type :teams
|
||||
:profile-id (:id profile1)}
|
||||
out (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (= 2 (count result)))
|
||||
(t/is (= (:id team) (get-in result [1 :id])))
|
||||
(t/is (= (:default-team-id profile1) (get-in result [0 :id])))))
|
||||
|
||||
;; Request team to be deleted
|
||||
(let [params {::th/type :delete-team
|
||||
:id (:id team)
|
||||
:profile-id (:id profile1)}
|
||||
out (th/mutation! params)]
|
||||
(t/is (nil? (:error out))))
|
||||
|
||||
;; query the list of teams after soft deletion
|
||||
(let [data {::th/type :teams
|
||||
:profile-id (:id profile1)}
|
||||
out (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (= 1 (count result)))
|
||||
(t/is (= (:default-team-id profile1) (get-in result [0 :id])))))
|
||||
|
||||
;; run permanent deletion (should be noop)
|
||||
(let [result (task {:max-age (dt/duration {:minutes 1})})]
|
||||
(t/is (nil? result)))
|
||||
|
||||
;; query the list of projects of a after hard deletion
|
||||
(let [data {::th/type :projects
|
||||
:team-id (:id team)
|
||||
:profile-id (:id profile1)}
|
||||
out (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (= 0 (count result)))))
|
||||
|
||||
;; run permanent deletion
|
||||
(let [result (task {:max-age (dt/duration 0)})]
|
||||
(t/is (nil? result)))
|
||||
|
||||
;; query the list of projects of a after hard deletion
|
||||
(let [data {::th/type :projects
|
||||
:team-id (:id team)
|
||||
:profile-id (:id profile1)}
|
||||
out (th/query! data)]
|
||||
;; (th/print-result! out)
|
||||
(let [error (:error out)
|
||||
error-data (ex-data error)]
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (= (:type error-data) :not-found))))
|
||||
))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue