diff --git a/common/src/app/common/files/migrations.cljc b/common/src/app/common/files/migrations.cljc index 28174ba84f..873aa1c3ab 100644 --- a/common/src/app/common/files/migrations.cljc +++ b/common/src/app/common/files/migrations.cljc @@ -1978,6 +1978,26 @@ (update :pages-index d/update-vals update-container) (d/update-when :components d/update-vals update-container)))) +(defmethod migrate-data "0026-fix-svg-raw-shapes-uuids" + ;; Before the svg-raw schema declared :shapes as a vector of uuid, + ;; the JSON decoder had no type information for those child ids and + ;; left them as plain strings on any round trip, so they got + ;; persisted as strings. Once the schema was tightened, such files + ;; fail schema validation; this migration parses the strings back + ;; into uuid instances. + [data _] + (letfn [(update-object [object] + (cond-> object + (cfh/svg-raw-shape? object) + (d/update-when :shapes #(mapv uuid/coerce %)))) + + (update-container [container] + (d/update-when container :objects d/update-vals update-object))] + + (-> data + (update :pages-index d/update-vals update-container) + (d/update-when :components d/update-vals update-container)))) + (def available-migrations (into (d/ordered-set) ["legacy-2" @@ -2060,4 +2080,5 @@ "0022-normalize-component-root-and-resync" "0023-repair-token-themes-with-inexistent-sets" "0024b-fix-stroke-cap-placement" - "0025-repair-empty-text-content"])) + "0025-repair-empty-text-content" + "0026-fix-svg-raw-shapes-uuids"])) diff --git a/common/test/common_tests/files_migrations_0026_test.cljc b/common/test/common_tests/files_migrations_0026_test.cljc new file mode 100644 index 0000000000..92dde088e8 --- /dev/null +++ b/common/test/common_tests/files_migrations_0026_test.cljc @@ -0,0 +1,110 @@ +;; 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 Sucursal en EspaƱa SL + +(ns common-tests.files-migrations-0026-test + (:require + [app.common.files.migrations :as cfm] + [app.common.uuid :as uuid] + [clojure.test :as t])) + +;; 0026-fix-svg-raw-shapes-uuids +;; Before the svg-raw schema declared :shapes as a vector of uuid, the +;; JSON decoder had no type information for those child ids and left +;; them as plain strings on any round trip, so they got persisted as +;; strings. Once the schema was tightened, such files fail schema +;; validation; this migration parses the strings back into uuids. + +(defn- make-svg-raw-shape + "Build a minimal svg-raw shape with the supplied :shapes vector. + When `shapes` is nil the :shapes key is omitted, like a leaf svg-raw + shape." + [shape-id shapes] + (cond-> {:id shape-id + :type :svg-raw} + (some? shapes) + (assoc :shapes shapes))) + +(defn- make-other-shape + "Build a minimal non-svg-raw shape that must stay untouched." + [shape-id shapes] + {:id shape-id + :type :group + :shapes shapes}) + +(t/deftest migration-0026-converts-svg-raw-shapes-strings-to-uuids-in-pages + (let [shape-id (uuid/next) + child-id (uuid/next) + page-id (uuid/next) + data {:pages-index + {page-id + {:objects + {shape-id (make-svg-raw-shape + shape-id + [(str child-id) + "1c2986ce-4a0f-8001-8007-1fb8f3b5ab31"])}}}} + data' (cfm/migrate-data data "0026-fix-svg-raw-shapes-uuids") + shape (get-in data' [:pages-index page-id :objects shape-id])] + + (t/is (= 2 (count (:shapes shape))) "child ids preserved") + (t/is (= child-id (first (:shapes shape))) "existing uuid string parsed to uuid") + (t/is (= #uuid "1c2986ce-4a0f-8001-8007-1fb8f3b5ab31" (second (:shapes shape))) + "foreign uuid string parsed to uuid") + (t/is (every? uuid? (:shapes shape)) "all child ids are uuids"))) + +(t/deftest migration-0026-converts-svg-raw-shapes-strings-to-uuids-in-components + (let [shape-id (uuid/next) + child-id (uuid/next) + component-id (uuid/next) + data {:components + {component-id + {:objects + {shape-id (make-svg-raw-shape + shape-id + [(str child-id) + "1c2986ce-4a0f-8001-8007-1fb92196e65f"])}}}} + data' (cfm/migrate-data data "0026-fix-svg-raw-shapes-uuids") + shape (get-in data' [:components component-id :objects shape-id])] + + (t/is (= 2 (count (:shapes shape))) "child ids preserved") + (t/is (= child-id (first (:shapes shape))) "existing uuid string parsed to uuid") + (t/is (= #uuid "1c2986ce-4a0f-8001-8007-1fb92196e65f" (second (:shapes shape))) + "foreign uuid string parsed to uuid") + (t/is (every? uuid? (:shapes shape)) "all child ids are uuids"))) + +(t/deftest migration-0026-leaves-uuids-and-other-shapes-untouched + (let [svg-raw-id (uuid/next) + child-id (uuid/next) + group-id (uuid/next) + leaf-id (uuid/next) + page-id (uuid/next) + data {:pages-index + {page-id + {:objects + {svg-raw-id (make-svg-raw-shape svg-raw-id [child-id]) + group-id (make-other-shape group-id [(str child-id)]) + leaf-id (make-svg-raw-shape leaf-id nil)}}}} + data' (cfm/migrate-data data "0026-fix-svg-raw-shapes-uuids") + objects (get-in data' [:pages-index page-id :objects])] + + (t/is (= [child-id] (:shapes (get objects svg-raw-id))) + "already-uuid svg-raw children untouched") + (t/is (= [(str child-id)] (:shapes (get objects group-id))) + "non-svg-raw shapes untouched") + (t/is (nil? (:shapes (get objects leaf-id))) + "svg-raw leaf without :shapes untouched"))) + +(t/deftest migration-0026-is-idempotent + (let [shape-id (uuid/next) + child-id (uuid/next) + page-id (uuid/next) + data {:pages-index + {page-id + {:objects + {shape-id (make-svg-raw-shape shape-id [(str child-id)])}}}} + data' (cfm/migrate-data data "0026-fix-svg-raw-shapes-uuids") + data'' (cfm/migrate-data data' "0026-fix-svg-raw-shapes-uuids")] + + (t/is (= data' data'') "second run is a no-op"))) \ No newline at end of file diff --git a/common/test/common_tests/runner.cljc b/common/test/common_tests/runner.cljc index b24b045e1a..cc08609837 100644 --- a/common/test/common_tests/runner.cljc +++ b/common/test/common_tests/runner.cljc @@ -19,6 +19,7 @@ [common-tests.files-builder-test] [common-tests.files-changes-test] [common-tests.files-migrations-0025-test] + [common-tests.files-migrations-0026-test] [common-tests.files-migrations-test] [common-tests.files.shapes-builder-test] [common-tests.files.validate-test] @@ -97,6 +98,7 @@ 'common-tests.files-changes-test 'common-tests.files-builder-test 'common-tests.files-migrations-0025-test + 'common-tests.files-migrations-0026-test 'common-tests.files-migrations-test 'common-tests.files.validate-test 'common-tests.geom-align-test