mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 19:28:12 +00:00
687 lines
32 KiB
Clojure
687 lines
32 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.rpc-nitrate-test
|
|
(:require
|
|
[app.common.uuid :as uuid]
|
|
[app.db :as-alias db]
|
|
[app.nitrate :as nitrate]
|
|
[app.rpc :as-alias rpc]
|
|
[app.rpc.commands.nitrate]
|
|
[backend-tests.helpers :as th]
|
|
[clojure.test :as t]
|
|
[cuerdas.core :as str]))
|
|
|
|
(t/use-fixtures :once th/state-init)
|
|
(t/use-fixtures :each th/database-reset)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Helpers
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defn- make-org-summary
|
|
[& {:keys [organization-id organization-name owner-id your-penpot-teams org-teams]
|
|
:or {your-penpot-teams [] org-teams []}}]
|
|
{:id organization-id
|
|
:name organization-name
|
|
:owner-id owner-id
|
|
:teams (into
|
|
(mapv (fn [id] {:id id :is-your-penpot true}) your-penpot-teams)
|
|
(mapv (fn [id] {:id id :is-your-penpot false}) org-teams))})
|
|
|
|
(defn- nitrate-call-mock
|
|
"Creates a mock for nitrate/call that returns the given org-summary for
|
|
:get-org-summary, a valid membership for :get-org-membership, and nil for
|
|
any other method."
|
|
[org-summary]
|
|
(fn [_cfg method _params]
|
|
(case method
|
|
:get-org-summary org-summary
|
|
:get-org-membership {:is-member true
|
|
:organization-id (:id org-summary)}
|
|
nil)))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Tests
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(t/deftest leave-org-happy-path-no-extra-teams
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
profile-user (th/create-profile* 2 {:is-active true})
|
|
|
|
org-default-team (th/create-team* 99 {:profile-id (:id profile-user)})
|
|
project (th/create-project* 99 {:profile-id (:id profile-user)
|
|
:team-id (:id org-default-team)})
|
|
_ (th/create-file* 99 {:profile-id (:id profile-user)
|
|
:project-id (:id project)})
|
|
|
|
organization-id (uuid/random)
|
|
;; The user's personal penpot team in the org context
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-user)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id your-penpot-id
|
|
:teams-to-delete []
|
|
:teams-to-leave []}
|
|
out (th/command! data)]
|
|
|
|
;; (th/print-result! out)
|
|
(t/is (th/success? out))
|
|
(t/is (nil? (:result out)))
|
|
|
|
;; The personal team must be renamed with the org prefix and
|
|
;; unset as a default team.
|
|
(let [team (th/db-get :team {:id your-penpot-id})]
|
|
(t/is (str/starts-with? (:name team) "[Test Org] "))
|
|
(t/is (false? (:is-default team))))))))
|
|
|
|
(t/deftest leave-org-deletes-org-default-team-when-empty
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
profile-user (th/create-profile* 2 {:is-active true})
|
|
org-default-team (th/create-team* 98 {:profile-id (:id profile-user)})
|
|
|
|
organization-id (uuid/random)
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-user)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id your-penpot-id
|
|
:teams-to-delete []
|
|
:teams-to-leave []}
|
|
out (th/command! data)]
|
|
|
|
(t/is (th/success? out))
|
|
|
|
;; Empty org default team should be soft-deleted.
|
|
(let [team (th/db-get :team {:id your-penpot-id} {::db/remove-deleted false})]
|
|
(t/is (some? (:deleted-at team))))))))
|
|
|
|
(t/deftest leave-org-keeps-and-renames-org-default-team-when-has-files
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
profile-user (th/create-profile* 2 {:is-active true})
|
|
org-default-team (th/create-team* 97 {:profile-id (:id profile-user)})
|
|
project (th/create-project* 97 {:profile-id (:id profile-user)
|
|
:team-id (:id org-default-team)})
|
|
_ (th/create-file* 97 {:profile-id (:id profile-user)
|
|
:project-id (:id project)})
|
|
|
|
organization-id (uuid/random)
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-user)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id your-penpot-id
|
|
:teams-to-delete []
|
|
:teams-to-leave []}
|
|
out (th/command! data)]
|
|
|
|
(t/is (th/success? out))
|
|
|
|
;; Non-empty org default team should remain and be renamed.
|
|
(let [team (th/db-get :team {:id your-penpot-id})]
|
|
(t/is (str/starts-with? (:name team) "[Test Org] "))
|
|
(t/is (false? (:is-default team)))
|
|
(t/is (nil? (:deleted-at team))))))))
|
|
|
|
(t/deftest leave-org-with-teams-to-delete
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
profile-user (th/create-profile* 2 {:is-active true})
|
|
;; profile-user is the sole owner/member of team1
|
|
team1 (th/create-team* 1 {:profile-id (:id profile-user)})
|
|
org-default-team (th/create-team* 99 {:profile-id (:id profile-user)})
|
|
|
|
organization-id (uuid/random)
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [(:id team1)])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-user)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id your-penpot-id
|
|
:teams-to-delete [(:id team1)]
|
|
:teams-to-leave []}
|
|
out (th/command! data)]
|
|
|
|
;; (th/print-result! out)
|
|
(t/is (th/success? out))
|
|
|
|
;; team1 should be scheduled for deletion (deleted-at set)
|
|
(let [team (th/db-get :team {:id (:id team1)} {::db/remove-deleted false})]
|
|
(t/is (some? (:deleted-at team))))))))
|
|
|
|
(t/deftest leave-org-with-ownership-transfer
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
profile-user (th/create-profile* 2 {:is-active true})
|
|
;; profile-user owns team1; profile-owner is also a member
|
|
team1 (th/create-team* 1 {:profile-id (:id profile-user)})
|
|
_ (th/create-team-role* {:team-id (:id team1)
|
|
:profile-id (:id profile-owner)
|
|
:role :editor})
|
|
org-default-team (th/create-team* 99 {:profile-id (:id profile-user)})
|
|
|
|
organization-id (uuid/random)
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [(:id team1)])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-user)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id your-penpot-id
|
|
:teams-to-delete []
|
|
:teams-to-leave [{:id (:id team1) :reassign-to (:id profile-owner)}]}
|
|
out (th/command! data)]
|
|
|
|
;; (th/print-result! out)
|
|
(t/is (th/success? out))
|
|
|
|
;; profile-user should no longer be a member of team1
|
|
(let [rel (th/db-get :team-profile-rel
|
|
{:team-id (:id team1)
|
|
:profile-id (:id profile-user)})]
|
|
(t/is (nil? rel)))
|
|
|
|
;; profile-owner should have been promoted to owner
|
|
(let [rel (th/db-get :team-profile-rel
|
|
{:team-id (:id team1)
|
|
:profile-id (:id profile-owner)})]
|
|
(t/is (true? (:is-owner rel))))))))
|
|
|
|
(t/deftest leave-org-exit-as-non-owner
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
profile-user (th/create-profile* 2 {:is-active true})
|
|
;; profile-owner owns team1; profile-user is a non-owner member
|
|
team1 (th/create-team* 1 {:profile-id (:id profile-owner)})
|
|
_ (th/create-team-role* {:team-id (:id team1)
|
|
:profile-id (:id profile-user)
|
|
:role :editor})
|
|
org-default-team (th/create-team* 99 {:profile-id (:id profile-user)})
|
|
|
|
organization-id (uuid/random)
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [(:id team1)])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-user)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id your-penpot-id
|
|
:teams-to-delete []
|
|
:teams-to-leave [{:id (:id team1)}]}
|
|
out (th/command! data)]
|
|
|
|
;; (th/print-result! out)
|
|
(t/is (th/success? out))
|
|
|
|
;; profile-user should no longer be a member of team1
|
|
(let [rel (th/db-get :team-profile-rel
|
|
{:team-id (:id team1)
|
|
:profile-id (:id profile-user)})]
|
|
(t/is (nil? rel)))
|
|
|
|
;; The team itself should still exist
|
|
(let [team (th/db-get :team {:id (:id team1)})]
|
|
(t/is (nil? (:deleted-at team))))))))
|
|
|
|
(t/deftest leave-org-error-org-owner-cannot-leave
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
org-default-team (th/create-team* 99 {:profile-id (:id profile-owner)})
|
|
organization-id (uuid/random)
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
;; profile-owner IS the org owner in the org-summary
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-owner)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id your-penpot-id
|
|
:teams-to-delete []
|
|
:teams-to-leave []}
|
|
out (th/command! data)]
|
|
|
|
(t/is (not (th/success? out)))
|
|
(t/is (= :validation (th/ex-type (:error out))))
|
|
(t/is (= :org-owner-cannot-leave (th/ex-code (:error out))))))))
|
|
|
|
(t/deftest leave-org-error-invalid-default-team-id
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
profile-user (th/create-profile* 2 {:is-active true})
|
|
org-default-team (th/create-team* 99 {:profile-id (:id profile-user)})
|
|
organization-id (uuid/random)
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
;; Pass a random UUID that is not in the your-penpot-teams list
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-user)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id (uuid/random)
|
|
:teams-to-delete []
|
|
:teams-to-leave []}
|
|
out (th/command! data)]
|
|
|
|
(t/is (not (th/success? out)))
|
|
(t/is (= :validation (th/ex-type (:error out))))
|
|
(t/is (= :not-valid-teams (th/ex-code (:error out))))))))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Unit Tests for calculate-valid-teams
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(def ^:private calculate-valid-teams
|
|
(or (ns-resolve 'app.rpc.commands.nitrate 'calculate-valid-teams)
|
|
(throw (ex-info "Unable to resolve calculate-valid-teams"
|
|
{:ns 'app.rpc.commands.nitrate
|
|
:symbol 'calculate-valid-teams}))))
|
|
|
|
(defn- make-team [id & {:keys [is-owner num-members member-ids]
|
|
:or {is-owner false num-members 1 member-ids []}}]
|
|
{:id id :is-owner is-owner :num-members num-members :member-ids member-ids})
|
|
|
|
(t/deftest calculate-valid-teams-no-org-teams
|
|
(let [default-id (uuid/random)
|
|
default-team (make-team default-id)
|
|
result (calculate-valid-teams [default-team] default-id)]
|
|
(t/is (= default-team (:valid-default-team result)))
|
|
(t/is (empty? (:valid-teams-to-delete-ids result)))
|
|
(t/is (empty? (:valid-teams-to-transfer result)))
|
|
(t/is (empty? (:valid-teams-to-exit result)))))
|
|
|
|
(t/deftest calculate-valid-teams-default-not-found
|
|
(let [default-id (uuid/random)
|
|
other-id (uuid/random)
|
|
other-team (make-team other-id)
|
|
;; default-id is not in org-teams at all
|
|
result (calculate-valid-teams [other-team] default-id)]
|
|
(t/is (nil? (:valid-default-team result)))))
|
|
|
|
(t/deftest calculate-valid-teams-sole-owner-team
|
|
(let [default-id (uuid/random)
|
|
team-id (uuid/random)
|
|
default (make-team default-id)
|
|
solo-team (make-team team-id :is-owner true :num-members 1)
|
|
result (calculate-valid-teams [default solo-team] default-id)]
|
|
(t/is (contains? (:valid-teams-to-delete-ids result) team-id))
|
|
(t/is (empty? (:valid-teams-to-transfer result)))
|
|
(t/is (empty? (:valid-teams-to-exit result)))))
|
|
|
|
(t/deftest calculate-valid-teams-owned-multi-member-team
|
|
(let [default-id (uuid/random)
|
|
team-id (uuid/random)
|
|
default (make-team default-id)
|
|
;; owner of a team with 3 members — must be transferred
|
|
multi-team (make-team team-id :is-owner true :num-members 3)
|
|
result (calculate-valid-teams [default multi-team] default-id)]
|
|
(t/is (empty? (:valid-teams-to-delete-ids result)))
|
|
(t/is (= [team-id] (map :id (:valid-teams-to-transfer result))))
|
|
(t/is (empty? (:valid-teams-to-exit result)))))
|
|
|
|
(t/deftest calculate-valid-teams-non-owner-multi-member-team
|
|
(let [default-id (uuid/random)
|
|
team-id (uuid/random)
|
|
default (make-team default-id)
|
|
;; non-owner member of a team with 2 members — can just exit
|
|
exit-team (make-team team-id :is-owner false :num-members 2)
|
|
result (calculate-valid-teams [default exit-team] default-id)]
|
|
(t/is (empty? (:valid-teams-to-delete-ids result)))
|
|
(t/is (empty? (:valid-teams-to-transfer result)))
|
|
(t/is (= [team-id] (map :id (:valid-teams-to-exit result))))))
|
|
|
|
(t/deftest calculate-valid-teams-mixed
|
|
(let [default-id (uuid/random)
|
|
solo-id (uuid/random)
|
|
transfer-id (uuid/random)
|
|
exit-id (uuid/random)
|
|
default (make-team default-id)
|
|
solo-team (make-team solo-id :is-owner true :num-members 1)
|
|
transfer-team (make-team transfer-id :is-owner true :num-members 2)
|
|
exit-team (make-team exit-id :is-owner false :num-members 3)
|
|
result (calculate-valid-teams [default solo-team transfer-team exit-team] default-id)]
|
|
(t/is (= #{solo-id} (:valid-teams-to-delete-ids result)))
|
|
(t/is (= [transfer-id] (map :id (:valid-teams-to-transfer result))))
|
|
(t/is (= [exit-id] (map :id (:valid-teams-to-exit result))))
|
|
(t/is (= default-id (:id (:valid-default-team result))))))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Integration: combined delete + leave
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(t/deftest leave-org-combined-delete-and-leave
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
profile-user (th/create-profile* 2 {:is-active true})
|
|
;; team1: profile-user is sole owner — must delete
|
|
team1 (th/create-team* 1 {:profile-id (:id profile-user)})
|
|
;; team2: profile-user owns it, profile-owner is also member — must transfer
|
|
team2 (th/create-team* 2 {:profile-id (:id profile-user)})
|
|
_ (th/create-team-role* {:team-id (:id team2)
|
|
:profile-id (:id profile-owner)
|
|
:role :editor})
|
|
;; team3: profile-owner owns it, profile-user is non-owner member — can exit
|
|
team3 (th/create-team* 3 {:profile-id (:id profile-owner)})
|
|
_ (th/create-team-role* {:team-id (:id team3)
|
|
:profile-id (:id profile-user)
|
|
:role :editor})
|
|
org-default-team (th/create-team* 99 {:profile-id (:id profile-user)})
|
|
|
|
organization-id (uuid/random)
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [(:id team1) (:id team2) (:id team3)])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-user)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id your-penpot-id
|
|
:teams-to-delete [(:id team1)]
|
|
:teams-to-leave [{:id (:id team2) :reassign-to (:id profile-owner)}
|
|
{:id (:id team3)}]}
|
|
out (th/command! data)]
|
|
|
|
(t/is (th/success? out))
|
|
|
|
;; team1 should be soft-deleted
|
|
(let [team (th/db-get :team {:id (:id team1)} {::db/remove-deleted false})]
|
|
(t/is (some? (:deleted-at team))))
|
|
|
|
;; profile-user should no longer be a member of team2
|
|
(let [rel (th/db-get :team-profile-rel {:team-id (:id team2) :profile-id (:id profile-user)})]
|
|
(t/is (nil? rel)))
|
|
|
|
;; profile-owner should now own team2
|
|
(let [rel (th/db-get :team-profile-rel {:team-id (:id team2) :profile-id (:id profile-owner)})]
|
|
(t/is (true? (:is-owner rel))))
|
|
|
|
;; profile-user should no longer be a member of team3
|
|
(let [rel (th/db-get :team-profile-rel {:team-id (:id team3) :profile-id (:id profile-user)})]
|
|
(t/is (nil? rel)))
|
|
|
|
;; team3 itself should still exist (profile-owner is still there)
|
|
(let [team (th/db-get :team {:id (:id team3)})]
|
|
(t/is (some? team)))))))
|
|
(t/deftest leave-org-error-teams-to-delete-incomplete
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
profile-user (th/create-profile* 2 {:is-active true})
|
|
;; profile-user is the sole owner/member of both team1 and team2
|
|
team1 (th/create-team* 1 {:profile-id (:id profile-user)})
|
|
team2 (th/create-team* 2 {:profile-id (:id profile-user)})
|
|
org-default-team (th/create-team* 99 {:profile-id (:id profile-user)})
|
|
|
|
organization-id (uuid/random)
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [(:id team1) (:id team2)])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
;; Only team1 is listed; team2 is also a sole-owner team and must be included
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-user)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id your-penpot-id
|
|
:teams-to-delete [(:id team1)]
|
|
:teams-to-leave []}
|
|
out (th/command! data)]
|
|
|
|
(t/is (not (th/success? out)))
|
|
(t/is (= :validation (th/ex-type (:error out))))
|
|
(t/is (= :not-valid-teams (th/ex-code (:error out))))))))
|
|
|
|
(t/deftest leave-org-error-cannot-delete-multi-member-team
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
profile-user (th/create-profile* 2 {:is-active true})
|
|
;; team1 has two members: profile-user (owner) and profile-owner (editor)
|
|
team1 (th/create-team* 1 {:profile-id (:id profile-user)})
|
|
_ (th/create-team-role* {:team-id (:id team1)
|
|
:profile-id (:id profile-owner)
|
|
:role :editor})
|
|
org-default-team (th/create-team* 99 {:profile-id (:id profile-user)})
|
|
|
|
organization-id (uuid/random)
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [(:id team1)])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
;; team1 has 2 members so it is not a valid deletion candidate
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-user)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id your-penpot-id
|
|
:teams-to-delete [(:id team1)]
|
|
:teams-to-leave []}
|
|
out (th/command! data)]
|
|
|
|
(t/is (not (th/success? out)))
|
|
(t/is (= :validation (th/ex-type (:error out))))
|
|
(t/is (= :not-valid-teams (th/ex-code (:error out))))))))
|
|
|
|
(t/deftest leave-org-error-teams-to-leave-incomplete
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
profile-user (th/create-profile* 2 {:is-active true})
|
|
;; profile-user owns team1, which also has profile-owner as editor
|
|
team1 (th/create-team* 1 {:profile-id (:id profile-user)})
|
|
_ (th/create-team-role* {:team-id (:id team1)
|
|
:profile-id (:id profile-owner)
|
|
:role :editor})
|
|
org-default-team (th/create-team* 99 {:profile-id (:id profile-user)})
|
|
|
|
organization-id (uuid/random)
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [(:id team1)])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
;; team1 must be transferred (owner + multiple members) but is absent
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-user)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id your-penpot-id
|
|
:teams-to-delete []
|
|
:teams-to-leave []}
|
|
out (th/command! data)]
|
|
|
|
(t/is (not (th/success? out)))
|
|
(t/is (= :validation (th/ex-type (:error out))))
|
|
(t/is (= :not-valid-teams (th/ex-code (:error out))))))))
|
|
|
|
(t/deftest leave-org-error-reassign-to-self
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
profile-user (th/create-profile* 2 {:is-active true})
|
|
team1 (th/create-team* 1 {:profile-id (:id profile-user)})
|
|
_ (th/create-team-role* {:team-id (:id team1)
|
|
:profile-id (:id profile-owner)
|
|
:role :editor})
|
|
org-default-team (th/create-team* 99 {:profile-id (:id profile-user)})
|
|
|
|
organization-id (uuid/random)
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [(:id team1)])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
;; reassign-to points to the profile that is leaving — not allowed
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-user)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id your-penpot-id
|
|
:teams-to-delete []
|
|
:teams-to-leave [{:id (:id team1) :reassign-to (:id profile-user)}]}
|
|
out (th/command! data)]
|
|
|
|
(t/is (not (th/success? out)))
|
|
(t/is (= :validation (th/ex-type (:error out))))
|
|
(t/is (= :not-valid-teams (th/ex-code (:error out))))))))
|
|
|
|
(t/deftest leave-org-error-reassign-to-non-member
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
profile-user (th/create-profile* 2 {:is-active true})
|
|
profile-other (th/create-profile* 3 {:is-active true})
|
|
;; team1 has profile-user (owner) and profile-owner (editor) — NOT profile-other
|
|
team1 (th/create-team* 1 {:profile-id (:id profile-user)})
|
|
_ (th/create-team-role* {:team-id (:id team1)
|
|
:profile-id (:id profile-owner)
|
|
:role :editor})
|
|
org-default-team (th/create-team* 99 {:profile-id (:id profile-user)})
|
|
|
|
organization-id (uuid/random)
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [(:id team1)])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
;; profile-other is not a member of team1
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-user)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id your-penpot-id
|
|
:teams-to-delete []
|
|
:teams-to-leave [{:id (:id team1) :reassign-to (:id profile-other)}]}
|
|
out (th/command! data)]
|
|
|
|
(t/is (not (th/success? out)))
|
|
(t/is (= :validation (th/ex-type (:error out))))
|
|
(t/is (= :not-valid-teams (th/ex-code (:error out))))))))
|
|
|
|
(t/deftest leave-org-error-reassign-on-non-owned-team
|
|
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
|
profile-user (th/create-profile* 2 {:is-active true})
|
|
;; profile-owner owns team1; profile-user is just a non-owner member
|
|
team1 (th/create-team* 1 {:profile-id (:id profile-owner)})
|
|
_ (th/create-team-role* {:team-id (:id team1)
|
|
:profile-id (:id profile-user)
|
|
:role :editor})
|
|
org-default-team (th/create-team* 99 {:profile-id (:id profile-user)})
|
|
|
|
organization-id (uuid/random)
|
|
your-penpot-id (:id org-default-team)
|
|
|
|
org-summary (make-org-summary
|
|
:organization-id organization-id
|
|
:organization-name "Test Org"
|
|
:owner-id (:id profile-owner)
|
|
:your-penpot-teams [your-penpot-id]
|
|
:org-teams [(:id team1)])]
|
|
|
|
(with-redefs [nitrate/call (nitrate-call-mock org-summary)]
|
|
;; profile-user is not the owner so providing reassign-to is invalid
|
|
(let [data {::th/type :leave-org
|
|
::rpc/profile-id (:id profile-user)
|
|
:id organization-id
|
|
:name "Test Org"
|
|
:default-team-id your-penpot-id
|
|
:teams-to-delete []
|
|
:teams-to-leave [{:id (:id team1) :reassign-to (:id profile-owner)}]}
|
|
out (th/command! data)]
|
|
|
|
(t/is (not (th/success? out)))
|
|
(t/is (= :validation (th/ex-type (:error out))))
|
|
(t/is (= :not-valid-teams (th/ex-code (:error out))))))))
|