Add nitrate api method get-remove-from-org-summary

This commit is contained in:
Pablo Alba 2026-04-20 09:30:41 +02:00 committed by Pablo Alba
parent ae66317d6c
commit 73b55ee47e
3 changed files with 181 additions and 1 deletions

View File

@ -176,7 +176,8 @@
:code :not-valid-teams))))
(defn leave-org [{:keys [::db/conn] :as cfg} {:keys [profile-id org-id org-name default-team-id teams-to-delete teams-to-leave skip-validation] :as params}]
(defn leave-org
[{:keys [::db/conn] :as cfg} {:keys [profile-id org-id org-name default-team-id teams-to-delete teams-to-leave skip-validation] :as params}]
(let [org-prefix (str "[" (d/sanitize-string org-name) "] ")
default-team-files-count (-> (db/exec-one! conn [sql:get-team-files-count default-team-id])

View File

@ -393,3 +393,33 @@ RETURNING id, name;")
(notifications/notify-user-removed-from-org cfg profile-id org-id org-name "dashboard.user-no-longer-belong-org")
nil))
;; API: get-remove-from-org-summary
(def ^:private schema:get-remove-from-org-summary-result
[:map
[:teams-to-delete ::sm/int]
[:teams-to-transfer ::sm/int]
[:teams-to-exit ::sm/int]])
(sv/defmethod ::get-remove-from-org-summary
"Get a summary of the teams that would be deleted, transferred, or exited
if the user were removed from the organization"
{::doc/added "2.16"
::sm/params [:map
[:profile-id ::sm/uuid]
[:org-id ::sm/uuid]
[:default-team-id ::sm/uuid]]
::sm/result schema:get-remove-from-org-summary-result
::db/transaction true}
[cfg {:keys [profile-id org-id default-team-id]}]
(let [{:keys [valid-teams-to-delete-ids
valid-teams-to-transfer
valid-teams-to-exit
valid-default-team]} (cnit/get-valid-teams cfg org-id profile-id default-team-id)]
(when-not valid-default-team
(ex/raise :type :validation
:code :not-valid-teams))
{:teams-to-delete (count valid-teams-to-delete-ids)
:teams-to-transfer (count valid-teams-to-transfer)
:teams-to-exit (count valid-teams-to-exit)}))

View File

@ -239,6 +239,9 @@
(fn [_cfg method _params]
(case method
:get-org-summary org-summary
:get-org-membership {:organization-id (:id org-summary)
:is-member true}
:remove-profile-from-org nil
nil)))
(t/deftest remove-from-org-happy-path-no-extra-teams
@ -438,3 +441,149 @@
(t/is (not (th/success? out)))
(t/is (= :validation (th/ex-type (:error out))))
(t/is (= :nobody-to-reassign-team (th/ex-code (:error out))))))
;; Tests: get-remove-from-org-summary
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(t/deftest get-remove-from-org-summary-no-extra-teams
;; User only has a default team — nothing to delete/transfer/exit.
(let [org-owner (th/create-profile* 1 {:is-active true})
user (th/create-profile* 2 {:is-active true})
org-team (th/create-team* 1 {:profile-id (:id user)})
org-id (uuid/random)
org-summary (make-org-summary
:org-id org-id
:org-name "Acme Org"
:owner-id (:id org-owner)
:your-penpot-teams [(:id org-team)]
:org-teams [])
out (with-redefs [nitrate/call (nitrate-call-mock org-summary)]
(management-command-with-nitrate!
{::th/type :get-remove-from-org-summary
::rpc/profile-id (:id org-owner)
:profile-id (:id user)
:org-id org-id
:default-team-id (:id org-team)}))]
(t/is (th/success? out))
(t/is (= {:teams-to-delete 0
:teams-to-transfer 0
:teams-to-exit 0}
(:result out)))))
(t/deftest get-remove-from-org-summary-with-teams-to-delete
;; User owns a sole-member extra org team → 1 to delete.
(let [org-owner (th/create-profile* 1 {:is-active true})
user (th/create-profile* 2 {:is-active true})
extra-team (th/create-team* 3 {:profile-id (:id user)})
org-team (th/create-team* 99 {:profile-id (:id user)})
org-id (uuid/random)
org-summary (make-org-summary
:org-id org-id
:org-name "Acme Org"
:owner-id (:id org-owner)
:your-penpot-teams [(:id org-team)]
:org-teams [(:id extra-team)])
out (with-redefs [nitrate/call (nitrate-call-mock org-summary)]
(management-command-with-nitrate!
{::th/type :get-remove-from-org-summary
::rpc/profile-id (:id org-owner)
:profile-id (:id user)
:org-id org-id
:default-team-id (:id org-team)}))]
(t/is (th/success? out))
(t/is (= {:teams-to-delete 1
:teams-to-transfer 0
:teams-to-exit 0}
(:result out)))))
(t/deftest get-remove-from-org-summary-with-teams-to-transfer
;; User owns a multi-member extra org team → 1 to transfer.
(let [org-owner (th/create-profile* 1 {:is-active true})
user (th/create-profile* 2 {:is-active true})
candidate (th/create-profile* 3 {:is-active true})
extra-team (th/create-team* 4 {:profile-id (:id user)})
_ (th/create-team-role* {:team-id (:id extra-team)
:profile-id (:id candidate)
:role :editor})
org-team (th/create-team* 99 {:profile-id (:id user)})
org-id (uuid/random)
org-summary (make-org-summary
:org-id org-id
:org-name "Acme Org"
:owner-id (:id org-owner)
:your-penpot-teams [(:id org-team)]
:org-teams [(:id extra-team)])
out (with-redefs [nitrate/call (nitrate-call-mock org-summary)]
(management-command-with-nitrate!
{::th/type :get-remove-from-org-summary
::rpc/profile-id (:id org-owner)
:profile-id (:id user)
:org-id org-id
:default-team-id (:id org-team)}))]
(t/is (th/success? out))
(t/is (= {:teams-to-delete 0
:teams-to-transfer 1
:teams-to-exit 0}
(:result out)))))
(t/deftest get-remove-from-org-summary-with-teams-to-exit
;; User is a non-owner member of an org team → 1 to exit.
(let [org-owner (th/create-profile* 1 {:is-active true})
user (th/create-profile* 2 {:is-active true})
extra-team (th/create-team* 5 {:profile-id (:id org-owner)})
_ (th/create-team-role* {:team-id (:id extra-team)
:profile-id (:id user)
:role :editor})
org-team (th/create-team* 99 {:profile-id (:id user)})
org-id (uuid/random)
org-summary (make-org-summary
:org-id org-id
:org-name "Acme Org"
:owner-id (:id org-owner)
:your-penpot-teams [(:id org-team)]
:org-teams [(:id extra-team)])
out (with-redefs [nitrate/call (nitrate-call-mock org-summary)]
(management-command-with-nitrate!
{::th/type :get-remove-from-org-summary
::rpc/profile-id (:id org-owner)
:profile-id (:id user)
:org-id org-id
:default-team-id (:id org-team)}))]
(t/is (th/success? out))
(t/is (= {:teams-to-delete 0
:teams-to-transfer 0
:teams-to-exit 1}
(:result out)))))
(t/deftest get-remove-from-org-summary-does-not-mutate
;; Calling the summary endpoint must not modify any teams.
(let [org-owner (th/create-profile* 1 {:is-active true})
user (th/create-profile* 2 {:is-active true})
extra-team (th/create-team* 6 {:profile-id (:id user)})
org-team (th/create-team* 99 {:profile-id (:id user)})
org-id (uuid/random)
org-summary (make-org-summary
:org-id org-id
:org-name "Acme Org"
:owner-id (:id org-owner)
:your-penpot-teams [(:id org-team)]
:org-teams [(:id extra-team)])
_ (with-redefs [nitrate/call (nitrate-call-mock org-summary)]
(management-command-with-nitrate!
{::th/type :get-remove-from-org-summary
::rpc/profile-id (:id org-owner)
:profile-id (:id user)
:org-id org-id
:default-team-id (:id org-team)}))]
;; Both teams must still exist and be undeleted
(let [t1 (th/db-get :team {:id (:id org-team)})]
(t/is (some? t1))
(t/is (nil? (:deleted-at t1))))
(let [t2 (th/db-get :team {:id (:id extra-team)})]
(t/is (some? t2))
(t/is (nil? (:deleted-at t2))))
;; User must still be a member of both teams
(let [rel1 (th/db-get :team-profile-rel {:team-id (:id org-team) :profile-id (:id user)})]
(t/is (some? rel1)))
(let [rel2 (th/db-get :team-profile-rel {:team-id (:id extra-team) :profile-id (:id user)})]
(t/is (some? rel2)))))