penpot/frontend/test/frontend_tests/data/workspace_media_test.cljs
2026-05-29 11:24:58 +02:00

190 lines
6.9 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 Sucursal en España SL
(ns frontend-tests.data.workspace-media-test
"Integration tests for the chunked-upload logic in
app.main.data.workspace.media."
(:require
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.workspace.media :as media]
[beicon.v2.core :as rx]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.http :as http]))
;; ---------------------------------------------------------------------------
;; Local helpers
;; ---------------------------------------------------------------------------
(defn- make-blob
"Creates a JS Blob of exactly `size` bytes with the given `mtype`."
[size mtype]
(let [buf (js/Uint8Array. size)]
(js/Blob. #js [buf] #js {:type mtype})))
;; ---------------------------------------------------------------------------
;; Small-file path: direct upload (no chunking)
;; ---------------------------------------------------------------------------
(t/deftest small-file-uses-direct-upload
(t/testing "blobs below chunk-size use :upload-file-media-object directly"
(t/async done
(let [file-id (uuid/next)
;; One byte below the threshold so the blob takes the direct path
blob-size (dec cf/upload-chunk-size)
blob (make-blob blob-size "image/jpeg")
calls (atom [])
fetch-mock
(fn [url _opts]
(let [cmd (http/url->cmd url)]
(swap! calls conj cmd)
(js/Promise.resolve
(http/make-json-response
{:id (str (uuid/next))
:name "img"
:width 100
:height 100
:mtype "image/jpeg"
:file-id (str file-id)}))))
orig (http/install-fetch-mock! fetch-mock)]
(->> (media/process-blobs
{:file-id file-id
:local? true
:blobs [blob]
:on-image (fn [_] nil)
:on-svg (fn [_] nil)})
(rx/subs!
(fn [_] nil)
(fn [err]
(t/is false (str "unexpected error: " (ex-message err)))
(done))
(fn []
(http/restore-fetch! orig)
;; Should call :upload-file-media-object, NOT the chunked API
(t/is (= 1 (count @calls)))
(t/is (= :upload-file-media-object (first @calls)))
(done))))))))
;; ---------------------------------------------------------------------------
;; Large-file path: chunked upload via uploads namespace
;; ---------------------------------------------------------------------------
(t/deftest large-file-uses-chunked-upload
(t/testing "blobs at or above chunk-size use the three-step session API"
(t/async done
(let [file-id (uuid/next)
session-id (uuid/next)
chunk-size cf/upload-chunk-size
;; Exactly two full chunks
blob-size (* 2 chunk-size)
blob (make-blob blob-size "image/jpeg")
calls (atom [])
fetch-mock
(fn [url _opts]
(let [cmd (http/url->cmd url)]
(swap! calls conj cmd)
(js/Promise.resolve
(http/make-json-response
(case cmd
:create-upload-session
{:session-id (str session-id)}
:upload-chunk
{:session-id (str session-id) :index 0}
:assemble-file-media-object
{:id (str (uuid/next))
:name "img"
:width 100
:height 100
:mtype "image/jpeg"
:file-id (str file-id)}
;; Default: return an error response
{:error (str "unexpected cmd: " cmd)})))))
orig (http/install-fetch-mock! fetch-mock)]
(->> (media/process-blobs
{:file-id file-id
:local? true
:blobs [blob]
:on-image (fn [_] nil)
:on-svg (fn [_] nil)})
(rx/subs!
(fn [_] nil)
(fn [err]
(t/is false (str "unexpected error: " (ex-message err)))
(done))
(fn []
(http/restore-fetch! orig)
(let [cmd-seq @calls]
;; First call must create the session
(t/is (= :create-upload-session (first cmd-seq)))
;; Two chunk uploads
(t/is (= 2 (count (filter #(= :upload-chunk %) cmd-seq))))
;; Last call must assemble
(t/is (= :assemble-file-media-object (last cmd-seq)))
;; Direct upload must NOT be called
(t/is (not (some #(= :upload-file-media-object %) cmd-seq))))
(done))))))))
(t/deftest chunked-upload-chunk-count-matches-blob
(t/testing "number of chunk upload calls equals ceil(blob-size / chunk-size)"
(t/async done
(let [file-id (uuid/next)
session-id (uuid/next)
chunk-size cf/upload-chunk-size
;; Three chunks: 2 full + 1 partial
blob-size (+ (* 2 chunk-size) 1)
blob (make-blob blob-size "image/jpeg")
chunk-calls (atom 0)
fetch-mock
(fn [url _opts]
(let [cmd (http/url->cmd url)]
(js/Promise.resolve
(http/make-json-response
(case cmd
:create-upload-session
{:session-id (str session-id)}
:upload-chunk
(do (swap! chunk-calls inc)
{:session-id (str session-id) :index 0})
:assemble-file-media-object
{:id (str (uuid/next))
:name "img"
:width 100
:height 100
:mtype "image/jpeg"
:file-id (str file-id)}
{:error (str "unexpected cmd: " cmd)})))))
orig (http/install-fetch-mock! fetch-mock)]
(->> (media/process-blobs
{:file-id file-id
:local? true
:blobs [blob]
:on-image (fn [_] nil)
:on-svg (fn [_] nil)})
(rx/subs!
(fn [_] nil)
(fn [err]
(t/is false (str "unexpected error: " (ex-message err)))
(done))
(fn []
(http/restore-fetch! orig)
(t/is (= 3 @chunk-calls))
(done))))))))