From 0509e2b9d22109804686781f9cc2c1b6be60753c Mon Sep 17 00:00:00 2001 From: Alonso Torres Date: Wed, 30 Sep 2026 17:56:01 +0200 Subject: [PATCH] :bug: Fix problem with connect library (#12002) --- frontend/src/app/plugins/library.cljs | 27 +++-- .../frontend_tests/plugins/library_test.cljs | 109 +++++++++++++++++- 2 files changed, 127 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/plugins/library.cljs b/frontend/src/app/plugins/library.cljs index 998f8b0b5c..56782eb1e2 100644 --- a/frontend/src/app/plugins/library.cljs +++ b/frontend/src/app/plugins/library.cljs @@ -1248,15 +1248,26 @@ (not (r/check-permission plugin-id "library:write")) (u/reject-not-valid reject :connectLibrary "Plugin doesn't have 'library:write' permission") - (not (string? library-id)) + (not (uuid/parse* library-id)) (u/reject-not-valid reject :connectLibrary library-id) :else - (let [file-id (:current-file-id @st/state) + (let [file-id (:current-file-id @st/state) + team-id (:current-team-id @st/state) library-id (uuid/parse library-id)] - (->> st/stream - (rx/filter (ptk/type? ::dwl/link-file-to-library-finished)) - (rx/take 1) - (rx/subs! #(resolve (library-proxy plugin-id library-id)) reject)) - (st/emit! (-> (dwl/link-file-to-library file-id library-id) - (se/add-event plugin-id)))))))))) + (->> (rp/cmd! :get-team-shared-files {:team-id team-id}) + (rx/subs! + (fn [shared-files] + ;; Only published libraries of the team can be linked + (if (or (= library-id file-id) + (not (some #(= library-id (:id %)) shared-files))) + (u/reject-not-valid reject :connectLibrary + (dm/str library-id " is not a published library")) + (do + (->> st/stream + (rx/filter (ptk/type? ::dwl/link-file-to-library-finished)) + (rx/take 1) + (rx/subs! #(resolve (library-proxy plugin-id library-id)) reject)) + (st/emit! (-> (dwl/link-file-to-library file-id library-id) + (se/add-event plugin-id)))))) + reject))))))))) diff --git a/frontend/test/frontend_tests/plugins/library_test.cljs b/frontend/test/frontend_tests/plugins/library_test.cljs index d951cddb05..70d3e3fb7d 100644 --- a/frontend/test/frontend_tests/plugins/library_test.cljs +++ b/frontend/test/frontend_tests/plugins/library_test.cljs @@ -8,16 +8,23 @@ (:require [app.common.types.component :as ctk] [app.common.uuid :as uuid] + [app.main.data.event :as ev] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.texts :as dwt] [app.main.data.workspace.variants :as dwv] + [app.main.repo :as rp] [app.main.store :as st] [app.plugins.library :as library] [app.plugins.register :as r] [app.plugins.text :as text] [app.plugins.utils :as u] + [app.util.object :as obj] + [beicon.v2.core :as rx] [cljs.test :as t :include-macros true] - [frontend-tests.helpers.mock :as mock])) + [cuerdas.core :as str] + [frontend-tests.helpers.async :as async] + [frontend-tests.helpers.mock :as mock] + [potok.v2.core :as ptk])) (def ^:private plugin-id "00000000-0000-0000-0000-000000000000") @@ -205,3 +212,103 @@ (t/is (= 1 (count @errors))) (t/is (= [plugin-id :setVariantProperty "Plugin doesn't have 'library:write' permission"] (first @errors))))))) + +;; --------------------------------------------------------------------------- +;; connectLibrary +;; --------------------------------------------------------------------------- + +(defn- shared-files-cmd + "Answers every RPC command asynchronously with `shared-files`, recording + the calls in `mock/rpc-calls`." + [shared-files] + (mock/stub + (fn [cmd params] + (swap! mock/rpc-calls conj {:cmd cmd :params params}) + (rx/observe-on :async (rx/of shared-files))))) + +(defn- settled + "Resolves with `{:resolved value}` or `{:rejected cause}` once `p` settles, + or with `{:timeout true}` when it stays pending for 2 seconds." + [p] + (js/Promise.race + #js [(.then p (fn [v] {:resolved v}) (fn [e] {:rejected e})) + (js/Promise. (fn [resolve] (js/setTimeout #(resolve {:timeout true}) 2000)))])) + +(t/deftest ^:async connect-library-links-a-published-library + (let [file-id (uuid/next) + team-id (uuid/next) + library-id (uuid/next) + stream (rx/subject) + events (atom [])] + (await + (mock/with-mocks* + {st/state (atom {:current-file-id file-id :current-team-id team-id}) + st/stream stream + st/emit! (mock/stub (fn [& emitted] (swap! events into emitted))) + rp/cmd! (shared-files-cmd [{:id library-id :name "Published"}]) + r/check-permission (mock/stub (constantly true))} + (let [context (library/library-subcontext plugin-id) + result (settled (.connectLibrary context (str library-id))) + link? #(= ::dwl/link-file-to-library (ptk/type %))] + (await (async/wait-for #(some link? @events) "link event emitted")) + (rx/push! stream (ptk/data-event ::dwl/link-file-to-library-finished)) + (let [{:keys [resolved]} (await result) + link-data (ev/-data (first (filter link? @events)))] + (t/is (= library-id (obj/get resolved "$id"))) + (t/is (= file-id (:file-id link-data))) + (t/is (= library-id (:library-id link-data))) + (t/is (= [{:cmd :get-team-shared-files :params {:team-id team-id}}] + @mock/rpc-calls)))))))) + +(t/deftest ^:async connect-library-rejects-an-unpublished-file + (let [file-id (uuid/next) + library-id (uuid/next) + events (atom [])] + (await + (mock/with-mocks* + {st/state (atom {:current-file-id file-id :current-team-id (uuid/next)}) + st/emit! (mock/stub (fn [& emitted] (swap! events into emitted))) + rp/cmd! (shared-files-cmd [{:id (uuid/next) :name "Other published"}]) + r/check-permission (mock/stub (constantly true))} + (let [context (library/library-subcontext plugin-id) + {:keys [rejected]} (await (settled (.connectLibrary context (str library-id))))] + (t/is (str/includes? (str rejected) "is not a published library")) + (t/is (empty? @events))))))) + +(t/deftest ^:async connect-library-rejects-the-current-file + (let [file-id (uuid/next) + events (atom [])] + (await + (mock/with-mocks* + {st/state (atom {:current-file-id file-id :current-team-id (uuid/next)}) + st/emit! (mock/stub (fn [& emitted] (swap! events into emitted))) + rp/cmd! (shared-files-cmd [{:id file-id :name "Current file"}]) + r/check-permission (mock/stub (constantly true))} + (let [context (library/library-subcontext plugin-id) + {:keys [rejected]} (await (settled (.connectLibrary context (str file-id))))] + (t/is (str/includes? (str rejected) "is not a published library")) + (t/is (empty? @events))))))) + +(t/deftest ^:async connect-library-rejects-an-invalid-id + (await + (mock/with-mocks* + {st/state (atom {:current-file-id (uuid/next) :current-team-id (uuid/next)}) + st/emit! mock/noop + rp/cmd! (shared-files-cmd []) + r/check-permission (mock/stub (constantly true))} + (let [context (library/library-subcontext plugin-id) + {:keys [rejected]} (await (settled (.connectLibrary context "not-a-uuid")))] + (t/is (str/includes? (str rejected) "not-a-uuid")) + (t/is (empty? @mock/rpc-calls)))))) + +(t/deftest ^:async connect-library-checks-permission + (await + (mock/with-mocks* + {st/state (atom {:current-file-id (uuid/next) :current-team-id (uuid/next)}) + st/emit! mock/noop + rp/cmd! (shared-files-cmd []) + r/check-permission (mock/stub (constantly false))} + (let [context (library/library-subcontext plugin-id) + {:keys [rejected]} (await (settled (.connectLibrary context (str (uuid/next)))))] + (t/is (str/includes? (str rejected) "Plugin doesn't have 'library:write' permission")) + (t/is (empty? @mock/rpc-calls))))))