Repository: CSCfi/rems
Pull request #3189 (2023-09-28 15:19:37)

feat: allow handler to redact handler attachments
relates #3190

Allow handler to redact also other handler attachments. Refactored `model/allowed-redact-roles` and `model/apply-attachment-permissions`, and amended api test with test case where handler redacts another handlers attachment.

Other changes
- handling users may now see which attachments were redacted in event/redacted-attachments

# Checklist for author

Remove items that aren't applicable, check items that are done.

## Reviewability
- [x] Link to issue
- [x] Consider adding screenshots for ease of review

## Backwards compatibility
- [x] Feature appears correctly in PDF print

## Documentation
- [x] Update changelog if necessary

## Testing
- [x] Complex logic is unit tested
- [x] Valuable features are integration / browser / acceptance tested automatically

## Follow-up
- [ ] New tasks are created for pending or remaining tasks

# Screenshots

## Handler

![Screenshot 2023-10-05 at 19 15 31](https://github.com/CSCfi/rems/assets/794087/873fbdfb-c17c-4726-8cf3-baf1273eb004)

![Screenshot 2023-10-05 at 19 19 23](https://github.com/CSCfi/rems/assets/794087/e20378a4-bfc4-453c-8348-fb330a0e5c74)

## Applicant

![Screenshot 2023-10-05 at 19 16 18](https://github.com/CSCfi/rems/assets/794087/3822b904-e6bb-4356-b15a-404d6190c9db)

![Screenshot 2023-10-05 at 19 18 53](https://github.com/CSCfi/rems/assets/794087/3d784807-c75d-49fe-86b5-6c5e20dd001b)


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15b223dad..dbb15702e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ Changes since v2.33
 
 ### Changes
 - "Show related events" has been removed from events. (#3156)
+- Handler can now redact other handlers attachments. (#3190)
 
 ### Additions
 - (Experimental) Workflow can be configured to enable voting for the approval. Currently all handlers can vote (including bots). Use `:enable-voting`. (#3174)
@@ -20,6 +21,7 @@ Changes since v2.33
 - Added experimental support for named format parameters in translations. (#3183)
 - Cache reloading can be configured using the new `:buzy-hours` config. REMS will then try to avoid reloading during the specified time spans. (#3194)
 - The handling users can now see whether an event is shown to the applicant from a small eye icon in the event history. (#3156)
+- Event now shows which attachments were redacted when viewing as handling user. (#3190)
 
 ### Fixes
 - Email template parameters for `:application-expiration-notification` event are now documented. The parameters are different from standard event email parameters, which may have caused confusion.
diff --git a/resources/translations/en.edn b/resources/translations/en.edn
index 89458663b..c42dcf47a 100644
--- a/resources/translations/en.edn
+++ b/resources/translations/en.edn
@@ -253,7 +253,9 @@
                  :last-activity "Last activity"
                  :latest-activity "Latest activity"
                  :my-applications "My Applications"
+                 :redacted-attachments "Redacted attachments"
                  :redacted-attachments-replaced "New attachments replace the redacted attachments."
+                 :replacing-attachments "New attachments"
                  :resource "Resource"
                  :state "State"
                  :states {:approved "Approved"
diff --git a/resources/translations/fi.edn b/resources/translations/fi.edn
index 90b6d74b4..05aa4b3ff 100644
--- a/resources/translations/fi.edn
+++ b/resources/translations/fi.edn
@@ -237,7 +237,9 @@
                  :last-activity "Muokattu"
                  :latest-activity "Viimeisin toimenpide"
                  :my-applications "Omat hakemukset"
+                 :redacted-attachments "Poistetut liitetiedostot"
                  :redacted-attachments-replaced "Uudet liitetiedostot korvaavat poistetut liitetiedostot."
+                 :replacing-attachments "Uudet liitetiedostot"
                  :resource "Resurssi"
                  :state "Tila"
                  :states {:approved "Hyväksytty"
diff --git a/resources/translations/sv.edn b/resources/translations/sv.edn
index 31cef5e05..4731fb2a3 100644
--- a/resources/translations/sv.edn
+++ b/resources/translations/sv.edn
@@ -237,7 +237,9 @@
                  :last-activity "Ändrat"
                  :latest-activity "Senaste aktivitet"
                  :my-applications "Mina ansökningar"
-                 :redacted-attachments-replaced "Nya bilagor byts ut gamla bilagor."
+                 :redacted-attachments "Borttagna bilagor"
+                 :redacted-attachments-replaced "Nya bilagor byts ut borttagna bilagor."
+                 :replacing-attachments "Nya bilagor"
                  :resource "Resurs"
                  :state "Status"
                  :states {:approved "Godkänd"
diff --git a/src/clj/rems/application/commands.clj b/src/clj/rems/application/commands.clj
index 10d2330f6..5f6174aca 100644
--- a/src/clj/rems/application/commands.clj
+++ b/src/clj/rems/application/commands.clj
@@ -445,7 +445,7 @@
         attachments (build-index {:keys [:attachment/id]} (:application/attachments application))
         forbidden-ids (->> redacted-ids
                            (keep #(get attachments %))
-                           (remove #(application-util/can-redact-attachment % roles (:actor cmd)))
+                           (remove #(application-util/can-redact-attachment? % roles (:actor cmd)))
                            (map :attachment/id))]
     (when (seq forbidden-ids)
       {:errors [{:type :forbidden-redact-attachments
diff --git a/src/clj/rems/application/model.clj b/src/clj/rems/application/model.clj
index 5e90552b9..d70e57040 100644
--- a/src/clj/rems/application/model.clj
+++ b/src/clj/rems/application/model.clj
@@ -2,10 +2,10 @@
   (:require [clojure.set :as set]
             [clojure.test :refer [deftest is testing]]
             [com.rpl.specter :refer [ALL transform select]]
-            [medley.core :refer [assoc-some distinct-by find-first map-vals update-existing update-existing-in]]
+            [medley.core :refer [distinct-by find-first map-vals update-existing update-existing-in]]
             [rems.application.events :as events]
             [rems.application.master-workflow :as master-workflow]
-            [rems.common.application-util :refer [applicant-and-members can-redact-attachment can-see-everything? is-applying-user?]]
+            [rems.common.application-util :refer [applicant-and-members can-redact-attachment? is-applying-user?]]
             [rems.common.form :as form]
             [rems.common.util :refer [assoc-some-in conj-vec getx getx-in into-vec]]
             [rems.permissions :as permissions]
@@ -768,26 +768,26 @@
 
       (permissions/blacklist application (permissions/compile-rules [{:permission :application.command/vote}])))))
 
-(defn- get-attachment-redact-roles [attachment application]
-  (when (:attachment/event attachment)
-    (let [attachment-user-roles (->> (getx-in attachment [:attachment/user :userid])
-                                     (permissions/user-roles application))]
-      (cond
-        (some #{:handler} attachment-user-roles)
-        #{}
+(defn- allowed-redact-roles [application attachment]
+  (let [event-id (get-in attachment [:attachment/event :event/id])
+        user (getx-in attachment [:attachment/user :userid])
+        roles (permissions/user-roles application user)
+        workflow (:application/workflow application)]
+    (cond
+      (not event-id) ; e.g. applicant attachment in form
+      #{}
 
-        (and (= :workflow/decider (get-in application [:application/workflow :workflow/type]))
-             (some #{:decider :past-decider} attachment-user-roles))
-        #{}
+      (and (= :workflow/decider (:workflow/type workflow))
+           (some #{:decider :past-decider} roles))
+      #{}
 
-        :else
-        #{:handler}))))
+      :else
+      #{:handler})))
 
 (defn- enrich-attachments [application get-user]
   (->> application
        (transform [:application/attachments ALL] #(update % :attachment/user get-user))
-       (transform [:application/attachments ALL] #(let [roles (get-attachment-redact-roles % application)]
-                                                    (assoc-some % :attachment/redact-roles roles)))))
+       (transform [:application/attachments ALL] #(assoc % :attachment/redact-roles (allowed-redact-roles application %)))))
 
 (defn enrich-with-injections
   [application {:keys [blacklisted?
@@ -896,32 +896,47 @@
   (->> application
        (transform [:application/forms ALL :form/fields ALL] #(apply-field-privacy % roles))))
 
-(defn- may-see-redacted-filename [roles]
-  (some #{:handler :reporter}
-        roles))
+(defn- hide-non-accessible-attachments [application]
+  (let [visible-ids (set (keys (classify-attachments application)))
+        visible? (comp visible-ids :attachment/id)]
+    (update application :application/attachments #(filterv visible? %))))
 
-(defn- apply-attachment-privacy [attachment roles userid]
-  (let [see-filename (or (not (:attachment/redacted attachment))
-                         (= userid (get-in attachment [:attachment/user :userid]))
-                         (may-see-redacted-filename roles))]
-    (cond-> attachment
-      (not see-filename) (assoc :attachment/filename :filename/redacted))))
+(defn- hide-redacted-filename [attachment permissions userid]
+  (cond
+    (not (:attachment/redacted attachment))
+    attachment
 
-(defn apply-privacy-by-user [application roles userid]
-  (->> application
-       (transform [:application/attachments ALL] #(apply-attachment-privacy % roles userid))))
+    (= userid (get-in attachment [:attachment/user :userid]))
+    attachment
+
+    (contains? permissions :see-everything)
+    attachment
 
-(defn apply-attachment-permissions [attachment roles userid]
-  (-> attachment
-      (assoc-some :attachment/can-redact (can-redact-attachment attachment roles userid))
-      (dissoc :attachment/redact-roles)))
+    :else (assoc attachment :attachment/filename :filename/redacted)))
+
+(defn- set-redact-permissions [attachment roles userid]
+  (if (:attachment/redacted attachment)
+    attachment
+    (assoc attachment :attachment/can-redact (can-redact-attachment? attachment roles userid))))
+
+(defn apply-attachments-privacy [application userid]
+  (let [roles (permissions/user-roles application userid)
+        permissions (permissions/user-permissions application userid)
+        see-everything? (contains? permissions :see-everything)]
+    (-> (if see-everything?
+          application
+          ;; applying users do not need to see event redacted attachments, only the latest
+          (update application :application/events (partial mapv #(dissoc % :event/redacted-attachments))))
+        (update :application/attachments (partial mapv #(hide-redacted-filename % permissions userid)))
+        (update :application/attachments (partial mapv #(set-redact-permissions % roles userid))))))
 
 (defn hide-non-public-information [application]
   (-> application
       hide-invitation-tokens
       ;; these are not used by the UI, so no need to expose them (especially the user IDs)
       (dissoc ::latest-review-request-by-user ::latest-decision-request-by-user)
-      (dissoc :application/past-members)))
+      (dissoc :application/past-members)
+      (update :application/attachments (partial mapv #(dissoc % :attachment/redact-roles)))))
 
 (defn- personalize-todo [application userid]
   (cond-> application
@@ -931,30 +946,6 @@
     (contains? (::latest-decision-request-by-user application) userid)
     (assoc :application/todo :waiting-for-your-decision)))
 
-(defn- visible-attachment-ids [application]
-  (->> application
-       classify-attachments
-       keys
-       set))
-
-(deftest test-visible-attachment-ids
-  (let [application {:application/events [{:event/type :application.event/foo
-                                           :event/attachments [{:attachment/id 1}
-                                                               {:attachment/id 3}]}
-                                          {:event/type :application.event/bar}]
-                     :application/forms [{:form/fields [{:field/type :attachment
-                                                         :field/value "5" :field/previous-value "7,15"}]}
-                                         {:form/fields [{:field/type :text
-                                                         :field/value "2" :field/previous-vaule "2"}
-                                                        {:field/type :attachment
-                                                         :field/value "9,11,13"}]}]}]
-    (is (= #{1 3 5 7 9 11 13 15} (visible-attachment-ids application)))))
-
-(defn- hide-attachments [application]
-  (let [visible-ids (visible-attachment-ids application)
-        visible? (comp visible-ids :attachment/id)]
-    (update application :application/attachments #(filterv visible? %))))
-
 (defn see-application? [application userid]
   (let [state (:application/state application)
         roles (permissions/user-roles application userid)]
@@ -977,11 +968,10 @@
             application
             (hide-sensitive-information application))
           (personalize-todo userid)
-          (hide-non-public-information)
           (apply-privacy-by-roles roles)
-          (apply-privacy-by-user roles userid)
-          (hide-attachments)
-          (update :application/attachments (partial map #(apply-attachment-permissions % roles userid)))
+          (hide-non-accessible-attachments)
+          (apply-attachments-privacy userid)
           (assoc :application/permissions permissions)
           (assoc :application/roles roles)
+          (hide-non-public-information)
           (permissions/cleanup)))))
diff --git a/src/clj/rems/css/styles.clj b/src/clj/rems/css/styles.clj
index 2926a589b..60875100f 100644
--- a/src/clj/rems/css/styles.clj
+++ b/src/clj/rems/css/styles.clj
@@ -346,6 +346,7 @@
     [:&:hover {:color (theme-getx :link-hover-color :color4)}]]
    [:.pointer {:cursor :pointer}
     [:label.form-check-label {:cursor :pointer}]]
+   [:.disabled (make-important {:opacity 1})] ; XXX: bootstrap default has too low contrast for accessibility
    [:html {:position :relative
            :min-width (u/px 320)
            :height (u/percent 100)}]
@@ -608,7 +609,6 @@
     [:&:hover {:filter "brightness(80%)"}]]
 
    (rems-table-styles)
-   [:.btn.disabled {:opacity 0.25}]
    [:.catalogue-item-link {:color "#fff"
                            :text-decoration "underline"}]
    [:.language-switcher {:padding ".5em 0"}]
@@ -854,10 +854,19 @@
    (actions-float-menu)
    [(s/descendant :.document :h3) {:margin-top (u/rem 4)}]
 
+   [:.break-newline {:white-space :pre-wrap}]
+
    [:.attachments {:display :flex
-                   :flex-flow "row wrap"
-                   :align-items :center
+                   :flex-direction :column
+                   :flex-wrap :nowrap
                    :gap (u/rem 0.5)}]
+   [:.attachment-link {:width (u/percent 100)
+                       :display :inline-flex
+                       :flex-wrap :nowrap
+                       :align-items :center}]
+
+   [:.download.disabled {:border-style :dashed}]
+   [(s/> :.download ":not(:first-child)") {:margin-left (u/rem 0.5)}]
 
    ;; print styling
    (stylesheet/at-media
diff --git a/src/clj/rems/pdf.clj b/src/clj/rems/pdf.clj
index 7a2ad32d3..03b75cb07 100644
--- a/src/clj/rems/pdf.clj
+++ b/src/clj/rems/pdf.clj
@@ -1,11 +1,11 @@
 (ns rems.pdf
   "Rendering applications as pdf"
-  (:require [clj-pdf.core :refer :all]
+  (:require [clj-pdf.core :refer [pdf]]
             [clj-time.core :as time]
             [clojure.string :as str]
             [rems.common.application-util :as application-util]
             [rems.common.form :as form]
-            [rems.common.util :refer [build-index]]
+            [rems.common.util :refer [build-index index-by]]
             [rems.config :refer [env]]
             [rems.context :as context]
             [rems.text :refer [localized localize-decision localize-event localize-attachment localize-state localize-time text text-format with-language]]
@@ -185,6 +185,19 @@
              (doall (for [field fields]
                       (render-field filenames field))))))))
 
+(defn- render-redacted-attachments [attachments-by-id redacted-attachments attachments]
+  (str "\n" (text-format :t.label/default
+                         (text :t.applications/redacted-attachments)
+                         (str/join ", " (for [id redacted-attachments
+                                              :let [att (get attachments-by-id id)]]
+                                          (localize-attachment (dissoc att :attachment/redacted)))))
+       (when (seq attachments)
+         (str "\n" (text-format :t.label/default
+                                (text :t.applications/replacing-attachments)
+                                (str/join ", " (for [id attachments
+                                                     :let [att (get attachments-by-id id)]]
+                                                 (localize-attachment att))))))))
+
 (defn- render-events [application]
   (let [filenames (attachment-filenames application)
         events (getx application :application/events)]
@@ -197,7 +210,9 @@
          (doall
           (for [event events
                 :when (not (#{:application.event/draft-saved}
-                            (:event/type event)))]
+                            (:event/type event)))
+                :let [attachment-ids (map :attachment/id (:event/attachments event))
+                      redacted-ids (map :attachment/id (:event/redacted-attachments event))]]
             [:phrase
              (localize-time (:event/time event)) " " (localize-event event)
              (when-let [decision (localize-decision event)]
@@ -206,12 +221,19 @@
                (str "\n" (text-format :t.label/default
                                       (text :t.form/comment)
                                       comment)))
-             (when-some [attachments (seq (:event/attachments event))]
+             (cond
+               (seq redacted-ids)
+               (render-redacted-attachments (->> (:application/attachments application)
+                                                 (index-by [:attachment/id]))
+                                            redacted-ids
+                                            attachment-ids)
+
+               (seq attachment-ids)
                (str "\n" (text-format :t.label/default
                                       (text :t.form/attachments)
-                                      (->> attachments
-                                           (map (comp filenames :attachment/id))
-                                           (str/join ", ")))))]))])])))
+                                      (str/join ", " (map filenames attachment-ids))))
+
+               :else nil)]))])])))
 
 (defn- render-application [application]
   [(env :pdf-metadata)
diff --git a/src/cljc/rems/common/application_util.cljc b/src/cljc/rems/common/application_util.cljc
index d9310482d..59652b32b 100644
--- a/src/cljc/rems/common/application_util.cljc
+++ b/src/cljc/rems/common/application_util.cljc
@@ -1,6 +1,6 @@
 (ns rems.common.application-util
   (:require [clojure.set]
-            [clojure.test :refer [deftest is]]
+            [clojure.test :refer [deftest is testing]]
             [rems.common.util :as util]))
 
 (def states
@@ -100,28 +100,41 @@
   (is (= [["ABC/" 2000 "." 1] ["ABC/" 2000 "." 2] ["ABC/" 2000 "." 3] ["ABC/" 2002 "." 0] ["ABC/" 2002 "." 2] ["ABC/" 2302 "." 0]]
          (sort (mapv parse-sortable-external-id ["ABC/2000.1" "ABC/2002.0" "ABC/2002.2" "ABC/2302.0" "ABC/2000.2" "ABC/2000.3"])))))
 
-(defn can-redact-attachment [attachment roles userid]
-  (when-not (:attachment/redacted attachment)
+(defn can-redact-attachment? [attachment roles userid]
+  (let [already-redacted (:attachment/redacted attachment)
+        event-id (get-in attachment [:attachment/event :event/id])
+        allowed-roles (:attachment/redact-roles attachment)]
     (cond
-      (nil? (get-in attachment [:attachment/event :event/id]))
-      false
+      already-redacted false
+
+      (not event-id) false
 
-      (some roles (:attachment/redact-roles attachment))
+      (= userid (get-in attachment [:attachment/user :userid]))
       true
 
-      :else
-      (= userid (get-in attachment [:attachment/user :userid])))))
-
-(deftest test-can-redact-attachment
-  (let [user "carl"
-        roles #{:handler}
-        attachment {:attachment/event {:event/id 1}
-                    :attachment/user {:userid user}
-                    :attachment/redact-roles roles}]
-    (is (nil? (can-redact-attachment (assoc attachment :attachment/redacted true) roles user)))
-    (is (not (can-redact-attachment nil nil nil)))
-    (is (not (can-redact-attachment (dissoc attachment :attachment/event) roles user)))
-    (is (not (can-redact-attachment attachment #{:logged-in} "alice")))
-    (is (can-redact-attachment attachment #{} user))
-    (is (can-redact-attachment attachment roles "handler"))))
+      (empty? (clojure.set/intersection (set roles)
+                                        (set allowed-roles)))
+      false
 
+      :else true)))
+
+(deftest test-can-redact-attachment?
+  (testing "redact roles and user can redact"
+    (let [reviewer-attachment {:attachment/event {:event/id 1}
+                               :attachment/user {:userid "reviewer"}
+                               :attachment/redact-roles #{:handler}}]
+      (is (can-redact-attachment? reviewer-attachment #{:reviewer} "reviewer"))
+      (is (can-redact-attachment? reviewer-attachment #{:handler} "handler"))
+      (is (not (can-redact-attachment? reviewer-attachment #{} "alice")))))
+
+  (testing "only user can redact"
+    (let [handler-attachment {:attachment/event {:event/id 2}
+                              :attachment/user {:userid "handler"}
+                              :attachment/redact-roles #{}}]
+      (is (can-redact-attachment? handler-attachment #{:handler} "handler"))
+      (is (not (can-redact-attachment? handler-attachment #{:handler} "assistant")))))
+
+  (testing "attachment without event cannot be redacted"
+    (let [applicant-attachment {:attachment/user {:userid "alice"}}]
+      (is (not (can-redact-attachment? applicant-attachment #{} "alice")))
+      (is (not (can-redact-attachment? applicant-attachment #{:handler} "handler"))))))
diff --git a/src/cljc/rems/text.cljc b/src/cljc/rems/text.cljc
index 3d433013e..4154ff4ce 100644
--- a/src/cljc/rems/text.cljc
+++ b/src/cljc/rems/text.cljc
@@ -256,9 +256,15 @@
   "If attachment is redacted, return localized text for redacted attachment.
    Otherwise return value of :attachment/filename."
   [attachment]
-  (if (= :filename/redacted (:attachment/filename attachment))
-    (text :t.applications/attachment-filename-redacted)
-    (:attachment/filename attachment)))
+  (let [filename (:attachment/filename attachment)]
+    (cond
+      (= :filename/redacted filename)
+      (text :t.applications/attachment-filename-redacted)
+
+      (:attachment/redacted attachment)
+      (text-format :t.label/parens filename (text :t.applications/attachment-filename-redacted))
+
+      :else filename)))
 
 (def ^:private localized-roles
   {;; :api-key
diff --git a/src/cljs/rems/application.cljs b/src/cljs/rems/application.cljs
index daa3e7769..d56b14b87 100644
--- a/src/cljs/rems/application.cljs
+++ b/src/cljs/rems/application.cljs
@@ -31,7 +31,7 @@
             [rems.administration.duo :refer [duo-field duo-info-field]]
             [rems.common.application-util :refer [accepted-licenses? can-see-everything? form-fields-editable? get-member-name is-handler? is-handling-user?]]
             [rems.common.attachment-util :as attachment-util]
-            [rems.atoms :refer [external-link expander file-download info-field readonly-checkbox document-title success-symbol make-empty-symbol]]
+            [rems.atoms :as atoms :refer [external-link expander file-download info-field readonly-checkbox document-title success-symbol make-empty-symbol]]
             [rems.common.catalogue-util :refer [catalogue-item-more-info-url]]
             [rems.collapsible :as collapsible]
             [rems.common.form :as form]
@@ -45,7 +45,7 @@
             [rems.guide-util :refer [component-info example lipsum lipsum-paragraphs]]
             [rems.phase :refer [phases]]
             [rems.spinner :as spinner]
-            [rems.text :refer [localize-decision localize-event localized localize-state localize-time localize-time-with-seconds text text-format]]
+            [rems.text :refer [localize-attachment localize-decision localize-event localized localize-state localize-time localize-time-with-seconds text text-format]]
             [rems.user :as user]
             [rems.util :refer [navigate! fetch post! focus-input-field focus-when-collapse-opened format-file-size]]))
 
@@ -675,7 +675,29 @@
 
     [:b (localize-event event)]))
 
-(defn- event-view [{:keys [attachments]} event]
+(defn- event-attachments [attachments redacted-attachments]
+  (cond
+    (seq redacted-attachments)
+    [:div.d-flex.flex-column.gap-1.pt-2
+     [:div.event-redacted-attachments
+      [:label (text :t.applications/redacted-attachments)]
+      [:div.break-newline
+       (->> redacted-attachments
+            (mapv #(localize-attachment (dissoc % :attachment/redacted)))
+            (interpose "\n")
+            (into [:<>]))]]
+     (when (seq attachments)
+       [:div.event-new-attachments
+        [:label (text :t.applications/replacing-attachments)]
+        [fields/render-attachments attachments]])]
+
+    (seq attachments)
+    [:div.event-new-attachments.pt-2
+     [fields/render-attachments attachments]]
+
+    :else nil))
+
+(defn- event-view [{:keys [attachments redacted-attachments]} event]
   (let [decision (localize-decision event)
         comment (case (:event/type event)
                   :application.event/copied-from
@@ -695,15 +717,15 @@
         [:div.event-decision decision])
       (when comment
         [:div.form-control.event-comment comment])
-      (when (seq attachments)
-        [:div.event-attachments.py-2
-         [fields/render-attachments attachments]])]]))
+      [event-attachments attachments redacted-attachments]]]))
 
 (defn- render-events [application events]
-  (let [attachments-by-event-id (->> (:application/attachments application)
-                                     (group-by #(get-in % [:attachment/event :event/id])))]
+  (let [attachments-by-event-id (group-by (comp :event/id :attachment/event) (:application/attachments application))
+        attachments-by-id (index-by [:attachment/id] (:application/attachments application))]
     (for [event events]
-      [event-view {:attachments (get attachments-by-event-id (:event/id event))}
+      [event-view {:attachments (get attachments-by-event-id (:event/id event))
+                   :redacted-attachments (vec (for [redacted (:event/redacted-attachments event)]
+                                                (get attachments-by-id (:attachment/id redacted))))}
        event])))
 
 (defn- get-application-phases [state]
@@ -1554,19 +1576,31 @@
                                 :event/type :application.event/remarked
                                 :event/actor-attributes {:name "Hannah Handler"}
                                 :application/comment (str lipsum "\n\nA final line.")}]))
-   (example "event with redacted attachments"
-            (let [opts {:attachments [{:attachment/filename "regular_file.pdf"}
-                                      {:attachment/filename "regular_file.pdf"
-                                       :attachment/redacted true}
-                                      {:attachment/filename :filename/redacted
-                                       :attachment/redacted true}]}]
-              [event-view opts {:event/time #inst "2020-01-01T08:35"
-                                :event/type :application.event/remarked
+   (example "event that redacts and replaces attachments"
+            (let [opts {:attachments [{:attachment/filename "new_image.jpeg"}
+                                      {:attachment/filename "new_document.pdf"}]
+                        :redacted-attachments [{:attachment/filename "image_alice.jpeg" :attachment/redacted true}
+                                               {:attachment/filename "document_alice.pdf" :attachment/redacted true}]}]
+              [event-view opts {:event/time #inst "2023-10-04T06:50"
+                                :event/type :application.event/attachments-redacted
                                 :event/actor-attributes {:name "Hannah Handler"}
                                 :application/comment (str lipsum "\n\nA final line.")}]))
+   (example "event where redacted attachments filenames are hidden"
+            (let [opts {:attachments (list {:attachment/filename :filename/redacted}
+                                           {:attachment/filename "document.pdf"}
+                                           {:attachment/filename :filename/redacted})}]
+              [event-view opts {:event/time #inst "2023-10-04T06:50"
+                                :event/type :application.event/remarked
+                                :event/actor-attributes {:name "Hannah Handler"}}]))
    (example "event with many attachments"
-            (let [opts {:attachments (repeat 30 {:attachment/filename "image.jpeg"})}]
-              [event-view opts {:event/time #inst "2020-01-01T08:35"
+            (let [opts {:attachments (list {:attachment/filename "document.txt"}
+                                           {:attachment/filename "agreement.doc" :attachment/redacted true}
+                                           {:attachment/filename "binding.834c530d-1a72-46c3-9593-f84269a915bf.pdf" :attachment/redacted true}
+                                           {:attachment/filename "document.txt"}
+                                           {:attachment/filename "document.7097ae1c-ba94-47b0-a551-9f127da5e277.jpeg"}
+                                           {:attachment/filename "document.6b10763b-f1b7-435d-aaf0-b327805f58fc.pdf" :attachment/redacted true}
+                                           {:attachment/filename "binding.important.txt"})}]
+              [event-view opts {:event/time #inst "2023-10-04T06:50"
                                 :event/type :application.event/approved
                                 :event/actor-attributes {:name "Hannah Handler"}}]))
 
diff --git a/src/cljs/rems/atoms.cljs b/src/cljs/rems/atoms.cljs
index 104b42bd2..2046d1ced 100644
--- a/src/cljs/rems/atoms.cljs
+++ b/src/cljs/rems/atoms.cljs
@@ -155,8 +155,9 @@
   so they are not supported.
 
   Additional options:
-  `:inline?` - puts the label and value on the same row
-  `:box?`    - wrap the value into a field value box (default true)"
+  `:inline?`     - puts the label and value on the same row
+  `:box?`        - wrap the value into a field value box (default true)
+  `:multiline?`  - puts the value in multiple rows, if possible. see `rems.atoms/format-field-values`"
   [title value & [{:keys [inline? box? multiline?] :or {box? true} :as _opts}]]
   (let [formatted-value (format-field-values value multiline?)
         style (cond box? {:class "form-control"}
@@ -170,28 +171,45 @@
        [:label title]
        [:div style formatted-value]])))
 
-(defn download-button [{:keys [title url]}]
-  [:a.attachment-link {:class "btn btn-outline-secondary text-truncate"
-                       :href url
-                       :target "_blank"}
-   [file-download]
-   [:span.ml-1 title]])
+(defn download-button [{:keys [disabled? title url]}]
+  (if disabled?
+    [:a.download.btn.btn-outline-secondary.text-truncate.disabled
+     {:disabled true
+      :role :button
+      :tabIndex "-1"}
+     [:span title]]
+
+    [:a.download.btn.btn-outline-secondary.text-truncate
+     {:href url
+      :role :button
+      :target "_blank"}
+     [file-download]
+     [:span title]]))
 
 (defn license-attachment-link
   "Renders link to the attachment with `id` and name `title`."
   [id title]
-  [download-button {:title title
-                    :url (str "/api/licenses/attachments/" id)}])
-
-(defn attachment-link
-  "Renders a link to attachment (should have keys :attachment/id and :attachment/filename).
-   If attachment is redacted, renders localized attachment instead."
-  [attachment]
-  (if (:attachment/redacted attachment)
-    [:div.attachment-link {:title (text :t.applications/attachment-filename-redacted)}
-     (localize-attachment attachment)]
-    [download-button {:title (localize-attachment attachment)
-                      :url (str "/applications/attachment/" (:attachment/id attachment))}]))
+  [:div.attachment-link
+   [download-button {:title title
+                     :url (str "/api/licenses/attachments/" id)}]])
+
+(defn attachment-link [attachment]
+  (let [filename (:attachment/filename attachment)]
+    (cond
+      (= :filename/redacted filename)
+      [:div.attachment-link
+       (text :t.applications/attachment-filename-redacted)]
+
+      (:attachment/redacted attachment)
+      [:div.attachment-link
+       [download-button {:disabled? true
+                         :title filename}]
+       [:div.col-auto (text :t.applications/attachment-filename-redacted)]]
+
+      :else
+      [:div.attachment-link
+       [download-button {:title (localize-attachment attachment)
+                         :url (str "/applications/attachment/" (:attachment/id attachment))}]])))
 
 (defn enrich-user [user]
   (assoc user :display (str (or (:name user)
@@ -384,7 +402,6 @@
                            :expanded? false
                            :content [:p "Expanded content"]}])
 
-
        (component-info action-link)
        (example "example command as link"
 
diff --git a/src/cljs/rems/fields.cljs b/src/cljs/rems/fields.cljs
index 4523d3f25..3480f6ee9 100644
--- a/src/cljs/rems/fields.cljs
+++ b/src/cljs/rems/fields.cljs
@@ -5,7 +5,7 @@
             [goog.functions :refer [debounce rateLimit]]
             [re-frame.core :as rf]
             [rems.administration.items :as items]
-            [rems.atoms :refer [add-symbol attachment-link close-symbol failure-symbol textarea]]
+            [rems.atoms :refer [add-symbol attachment-link download-button close-symbol failure-symbol textarea]]
             [rems.common.attachment-util :as attachment-util]
             [rems.common.form :as common-form]
             [rems.common.util :refer [assoc-not-present build-index getx]]
@@ -37,7 +37,6 @@
                                       :tab-index "-1"}
     content]])
 
-
 (defn- diff [value previous-value]
   (let [dmp (diff_match_patch.)
         diff (.diff_main dmp
@@ -333,7 +332,7 @@
   (let [upload-id (str id "-input")
         info-id (str id "-info")
         config @(rf/subscribe [:rems.config/config])]
-    [:div.upload-file.mr-2
+    [:div.upload-file
      [:input {:style {:display "none"}
               :type "file"
               :id upload-id
@@ -370,22 +369,22 @@
   [:div.form-group
    (when label
      [:label label])
-   (into [:<>]
+   (into [:div.attachments.form-group]
          (for [attachment attachments]
-           [:div.flex-row.d-flex.mb-2
-            [attachment-link attachment]
-            [:button.btn.btn-outline-secondary.ml-2
-             {:class (str "remove-attachment-" id)
-              :type :button
-              :on-click #(on-remove-attachment (:attachment/id attachment))}
-             [close-symbol]
-             " "
-             (text :t.form/attachment-remove)]]))
+           [:div.attachment-link
+            [download-button {:title (:attachment/filename attachment)
+                              :url (str "/applications/attachment/" (:attachment/id attachment))}]
+            [:div.col-auto
+             [:button.remove.btn.btn-outline-secondary
+              {:on-click #(on-remove-attachment (:attachment/id attachment))}
+              [close-symbol]
+              " "
+              (text :t.form/attachment-remove)]]]))
    [upload-button (str "upload-" id) status on-attach]])
 
 (defn render-attachments [attachments]
   (into [:div.attachments]
-        (for [attachment attachments]
+        (for [attachment (sort-by :attachment/id attachments)]
           [attachment-link attachment])))
 
 (defn attachment-field
@@ -533,6 +532,7 @@
                                     :on-attach (fn [_] nil)}])
    (example "multiple attachments, long filenames"
             [multi-attachment-view {:id "action-guide-example-1"
+                                    :label "Long filenames"
                                     :attachments [{:attachment/filename "this_is_the_very_very_very_long_filename_of_a_test_file_the_file_itself_is_quite_short_though_abcdefghijklmnopqrstuvwxyz0123456789_overflow_overflow_overflow.txt"}
                                                   {:attachment/filename "this_is_another_very_very_very_long_filename_of_another_test_file_the_file_itself_is_quite_short_though_abcdefghijklmnopqrstuvwxyz0123456789_overflow_overflow_overflow.txt"}]
                                     :on-attach (fn [_] nil)}])


Test patch:
diff --git a/src/clj/rems/db/test_data_helpers.clj b/src/clj/rems/db/test_data_helpers.clj
index ca7472641..daf2f9ee7 100644
--- a/src/clj/rems/db/test_data_helpers.clj
+++ b/src/clj/rems/db/test_data_helpers.clj
@@ -333,7 +333,7 @@
 
 (defn assert-no-existing-data! []
   (assert (empty? (db/get-organizations {}))
-          "You have existing oranizations, refusing to continue. An empty database is needed.")
+          "You have existing organizations, refusing to continue. An empty database is needed.")
   (assert (empty? (db/get-application-events {}))
           "You have existing applications, refusing to continue. An empty database is needed.")
   (assert (empty? (db/get-catalogue-items {}))
diff --git a/src/clj/rems/db/test_data_users.clj b/src/clj/rems/db/test_data_users.clj
index 8f8a9f92e..032d6275e 100644
--- a/src/clj/rems/db/test_data_users.clj
+++ b/src/clj/rems/db/test_data_users.clj
@@ -1,6 +1,5 @@
 (ns rems.db.test-data-users
-  (:require [clojure.test :refer :all]
-            [rems.application.approver-bot :as approver-bot]
+  (:require [rems.application.approver-bot :as approver-bot]
             [rems.application.bona-fide-bot :as bona-fide-bot]
             [rems.application.rejecter-bot :as rejecter-bot]
             [rems.application.expirer-bot :as expirer-bot]))
@@ -27,6 +26,7 @@
    :owner "owner"
    :reporter "reporter"
    :reviewer "carl"
+   :decider "diana"
    :roleless1 "elsa"
    :roleless2 "frank"
    :johnsmith "johnsmith"
@@ -38,6 +38,7 @@
    "malice" {:userid "malice" :email "malice@example.com" :name "Malice Applicant" :twinOf "alice" :other "Attribute Value" :mappings {"alt-id" "malice-alt-id"}}
    "handler" {:userid "handler" :email "handler@example.com" :name "Hannah Handler" :mappings {"alt-id" "handler-alt-id"}}
    "carl" {:userid "carl" :email "carl@example.com" :name "Carl Reviewer" :mappings {"alt-id" "carl-alt-id"}}
+   "diana" {:userid "diana" :email "diana@example.com" :name "Diana Decider"}
    "elsa" {:userid "elsa" :email "elsa@example.com" :name "Elsa Roleless" :mappings {"alt-id" "elsa-alt-id"}}
    "frank" {:userid "frank" :email "frank@example.com" :name "Frank Roleless" :organizations [{:organization/id "frank"}]}
    "organization-owner1" {:userid "organization-owner1" :email "organization-owner1@example.com" :name "Organization Owner 1"}
@@ -53,6 +54,7 @@
    "malice" {:sub "malice" :email "malice@example.com" :name "Malice Applicant" :twinOf "alice" :other "Attribute Value"}
    "handler" {:sub "handler" :email "handler@example.com" :name "Hannah Handler"}
    "carl" {:sub "carl" :email "carl@example.com" :name "Carl Reviewer"}
+   "diana" {:sub "diana" :email "diana@example.com" :name "Diana Decider"}
    "elsa" {:sub "elsa" :email "elsa@example.com" :name "Elsa Roleless"}
    "frank" {:sub "frank" :email "frank@example.com" :name "Frank Roleless" :organizations [{:organization/id "frank"}]}
    "organization-owner1" {:sub "organization-owner1" :email "organization-owner1@example.com" :name "Organization Owner 1"}
@@ -87,6 +89,8 @@
              :description "Owner is the user who owns all the resources and manages them in the administration. They can create the catalogue of resources, workflows, forms etc. They can also delegate this power to other users and set who handles which applications, but aren't necessarily the person who does this work themselves."}
             {:userid "carl"
              :description "Carl is an expert, who is sometimes asked to review applications, thus taking a small part in the handling process."}
+            {:userid "diana"
+             :description "Diana is an expert, who is sometimes asked to decide whether applications should be approved or rejected, thus taking a small part in the handling process."}
             {:userid "reporter"
              :description "Reporter is an administrative users, who is only interested in counting beans and applications, having no part in the actual handling."}]}
 
@@ -120,6 +124,7 @@
    :approver1 "RDapprover1@funet.fi"
    :approver2 "RDapprover2@funet.fi"
    :reviewer "RDreview@funet.fi"
+   :decider "RDdecider@funet.fi"
    :organization-owner1 "RDorganizationowner1@funet.fi"
    :organization-owner2 "RDorganizationowner2@funet.fi"
    :owner "RDowner@funet.fi"
@@ -131,6 +136,7 @@
    "RDapprover1@funet.fi" {:userid "RDapprover1@funet.fi" :email "RDapprover1.test@rems_example.org" :name "RDapprover1 REMSDEMO"}
    "RDapprover2@funet.fi" {:userid "RDapprover2@funet.fi" :email "RDapprover2.test@rems_example.org" :name "RDapprover2 REMSDEMO"}
    "RDreview@funet.fi" {:userid "RDreview@funet.fi" :email "RDreview.test@rems_example.org" :name "RDreview REMSDEMO"}
+   "RDdecider@funet.fi" {:userid "RDdecider@funet.fi" :email "RDdecider.test@rems_example.org" :name "RDdecider REMSDEMO"}
    "RDowner@funet.fi" {:userid "RDowner@funet.fi" :email "RDowner.test@test_example.org" :name "RDowner REMSDEMO"}
    "RDorganizationowner1@funet.fi" {:userid "RDorganizationowner1@funet.fi" :email "RDorganizationowner1.test@test_example.org" :name "RDorganizationowner1 REMSDEMO" :organizations [{:organization/id "organization1"}]}
    "RDorganizationowner2@funet.fi" {:userid "RDorganizationowner2@funet.fi" :email "RDorganizationowner2.test@test_example.org" :name "RDorganizationowner2 REMSDEMO" :organizations [{:organization/id "organization2"}]}
@@ -142,6 +148,7 @@
    "RDapprover1@funet.fi" {:sub "RDapprover1@funet.fi" :email "RDapprover1.test@rems_example.org" :name "RDapprover1 REMSDEMO"}
    "RDapprover2@funet.fi" {:sub "RDapprover2@funet.fi" :email "RDapprover2.test@rems_example.org" :name "RDapprover2 REMSDEMO"}
    "RDreview@funet.fi" {:sub "RDreview@funet.fi" :email "RDreview.test@rems_example.org" :name "RDreview REMSDEMO"}
+   "RDdecider@funet.fi" {:userid "RDdecider@funet.fi" :email "RDdecider.test@rems_example.org" :name "RDdecider REMSDEMO"}
    "RDowner@funet.fi" {:sub "RDowner@funet.fi" :email "RDowner.test@test_example.org" :name "RDowner REMSDEMO"}
    "RDorganizationowner1@funet.fi" {:sub "RDorganizationowner1@funet.fi" :email "RDorganizationowner1.test@test_example.org" :name "RDorganizationowner1 REMSDEMO" :organizations [{:organization/id "organization1"}]}
    "RDorganizationowner2@funet.fi" {:sub "RDorganizationowner2@funet.fi" :email "RDorganizationowner2.test@test_example.org" :name "RDorganizationowner2 REMSDEMO" :organizations [{:organization/id "organization2"}]}
diff --git a/src/clj/rems/service/test_data.clj b/src/clj/rems/service/test_data.clj
index 04a4e0dd7..882853666 100644
--- a/src/clj/rems/service/test_data.clj
+++ b/src/clj/rems/service/test_data.clj
@@ -281,14 +281,8 @@
                                                 :title "Decider workflow"
                                                 :type :workflow/decider
                                                 :handlers handlers
-                                                :licenses [link text]})
-        decider2 (test-helpers/create-workflow! {:actor owner
-                                                 :organization {:organization/id "nbn"}
-                                                 :title "Decider workflow with one handler"
-                                                 :type :workflow/decider
-                                                 :handlers [approver2 rejecter-bot]
-                                                 :licenses [link text]
-                                                 :voting {:type :handlers-vote}})
+                                                :licenses [link text]
+                                                :voting {:type :handlers-vote}})
         master (test-helpers/create-workflow! {:actor owner
                                                :organization {:organization/id "nbn"}
                                                :title "Master workflow"
@@ -336,7 +330,6 @@
     {:default default
      :ega ega
      :decider decider
-     :decider2 decider2
      :master master
      :auto-approve auto-approve
      :organization-owner organization-owner
@@ -1040,8 +1033,9 @@
     ;; create application to demo attachment redaction feature
     (let [applicant (:applicant1 users)
           member (:applicant2 users)
-          decider (:approver1 users)
-          handler (:approver2 users)
+          decider (:decider users)
+          handler (:approver2 users) ; "handler"
+          handler2 (:approver1 users) ; "developer"
           reviewer (:reviewer users)
           form-id (test-helpers/create-form! {:actor owner
                                               :organization {:organization/id "nbn"}
@@ -1069,7 +1063,7 @@
                                                        :resource-id resource-id
                                                        :form-id form-id
                                                        :organization {:organization/id "nbn"}
-                                                       :workflow-id (:decider2 workflows)
+                                                       :workflow-id (:decider workflows)
                                                        :categories [special-category]})
           app-id (test-helpers/create-draft! applicant [cat-id] "redacted attachments")]
       (test-helpers/invite-and-accept-member! {:actor applicant
@@ -1094,44 +1088,51 @@
                               :attachments [{:attachment/id (test-helpers/create-attachment! {:actor handler
                                                                                               :application-id app-id
                                                                                               :filename (str "handler_" (random-long-string 5) ".pdf")})}]})
-      (let [reviewer-attachments (repeatedly 3 #(test-helpers/create-attachment! {:actor reviewer
-                                                                                  :application-id app-id
-                                                                                  :filename "reviewer_attachment.pdf"}))]
-        (test-helpers/command! {:type :application.command/review
+      (test-helpers/command! {:type :application.command/review
+                              :application-id app-id
+                              :actor reviewer
+                              :comment "here are my thoughts. see attachments for details"
+                              :attachments [{:attachment/id (test-helpers/create-attachment! {:actor reviewer
+                                                                                              :application-id app-id
+                                                                                              :filename "reviewer_attachment.pdf"})}]})
+      (let [handler2-attachments (vec (for [att ["process_document_one.pdf" "process_document_two.pdf" "process_document_three.pdf"]]
+                                        {:attachment/id (test-helpers/create-attachment! {:actor handler2
+                                                                                          :application-id app-id
+                                                                                          :filename att})}))]
+        (test-helpers/command! {:type :application.command/remark
                                 :application-id app-id
-                                :actor reviewer
-                                :comment "here are my thoughts. see attachments for details"
-                                :attachments (vec (for [id reviewer-attachments]
-                                                    {:attachment/id id}))})
-        (test-helpers/command! {:type :application.command/redact-attachments
+                                :actor handler2
+                                :comment "see the attached process documents"
+                                :public true
+                                :attachments handler2-attachments})
+        (test-helpers/command! {:type :application.command/request-decision
+                                :application-id app-id
+                                :actor handler
+                                :comment "please decide, here are my final notes"
+                                :deciders [decider]
+                                :attachments [{:attachment/id (test-helpers/create-attachment! {:actor handler
+                                                                                                :application-id app-id
+                                                                                                :filename "handler_attachment.pdf"})}]})
+        (test-helpers/command! {:type :application.command/remark
                                 :application-id app-id
-                                :actor reviewer
-                                :comment "accidentally uploaded wrong attachments, here are the correct ones"
+                                :actor decider
+                                :comment "thank you, i will make my decision soon"
                                 :public false
-                                :redacted-attachments [{:attachment/id (first reviewer-attachments)}
-                                                       {:attachment/id (nth reviewer-attachments 2)}]
-                                :attachments [{:attachment/id (test-helpers/create-attachment! {:actor reviewer
+                                :attachments [{:attachment/id (test-helpers/create-attachment! {:actor decider
+                                                                                                :application-id app-id
+                                                                                                :filename "decider_attachment.pdf"})}]})
+        (test-helpers/command! {:type :application.command/redact-attachments
+                                :application-id app-id
+                                :actor handler
+                                :comment "updated the process documents to latest version"
+                                :public true
+                                :redacted-attachments (vec (rest handler2-attachments))
+                                :attachments [{:attachment/id (test-helpers/create-attachment! {:actor handler
                                                                                                 :application-id app-id
-                                                                                                :filename "reviewer_attachment.pdf"})}
-                                              {:attachment/id (test-helpers/create-attachment! {:actor reviewer
+                                                                                                :filename "process_document_two.pdf"})}
+                                              {:attachment/id (test-helpers/create-attachment! {:actor handler
                                                                                                 :application-id app-id
-                                                                                                :filename "reviewer_attachment.pdf"})}]}))
-      (test-helpers/command! {:type :application.command/request-decision
-                              :application-id app-id
-                              :actor handler
-                              :comment "please decide, here are my final notes"
-                              :deciders [decider]
-                              :attachments [{:attachment/id (test-helpers/create-attachment! {:actor handler
-                                                                                              :application-id app-id
-                                                                                              :filename "handler_attachment.pdf"})}]})
-      (test-helpers/command! {:type :application.command/remark
-                              :application-id app-id
-                              :actor decider
-                              :comment "thank you, i will make my decision soon"
-                              :public false
-                              :attachments [{:attachment/id (test-helpers/create-attachment! {:actor decider
-                                                                                              :application-id app-id
-                                                                                              :filename "decider_attachment.pdf"})}]}))))
+                                                                                                :filename "process_document_three.pdf"})}]})))))
 
 (defn create-organizations! [users]
   (let [owner (users :owner)
diff --git a/test/clj/rems/api/test_applications.clj b/test/clj/rems/api/test_applications.clj
index 07d0fe15d..eb366e3da 100644
--- a/test/clj/rems/api/test_applications.clj
+++ b/test/clj/rems/api/test_applications.clj
@@ -8,13 +8,11 @@
             [rems.api.testing :refer [api-call api-fixture api-response assert-response-is-ok authenticate get-csrf-token login-with-cookies read-body read-ok-body response-is-forbidden? response-is-not-found? response-is-ok? response-is-payload-too-large? response-is-unauthorized? response-is-unsupported-media-type? transit-body]]
             [rems.config]
             [rems.db.applications]
-            [rems.db.attachments]
             [rems.db.blacklist :as blacklist]
             [rems.db.core :as db]
             [rems.service.test-data :as test-data :refer [+test-api-key+]]
             [rems.db.test-data-helpers :as test-helpers]
             [rems.handler :refer [handler]]
-            [rems.json]
             [rems.testing-util :refer [with-fixed-time with-user]]
             [ring.mock.request :refer [header json-body request]])
   (:import java.io.ByteArrayOutputStream
@@ -1922,12 +1920,13 @@
 
 (deftest test-application-redact-attachments
   (let [applicant "alice"
-        handler-id "handler"
-        decider "developer"
+        handler1 "handler"
+        integration (test-helpers/create-user! {:userid "integration" :name nil :email nil})
+        decider "diana"
         reviewer "carl"
         form-id (test-helpers/create-form! {:form/fields [test-data/attachment-field]})
-        workflow-id (test-helpers/create-workflow! {:type :workflow/decider
-                                                    :handlers [handler-id]})
+        workflow-id (test-helpers/create-workflow! {:type :workflow/default
+                                                    :handlers [handler1 integration]})
         cat-item-id (test-helpers/create-catalogue-item! {:workflow-id workflow-id
                                                           :form-id form-id})
         app-id (test-helpers/create-application! {:catalogue-item-ids [cat-item-id]
@@ -1935,139 +1934,207 @@
         test-context (atom {})]
 
     (testing "setup test data"
-      (swap! test-context assoc ::applicant-att-id (upload-api-attachment-ok app-id applicant "applicant.txt"))
+      (swap! test-context assoc applicant (upload-api-attachment-ok app-id applicant "applicant.txt"))
       (is (= {:success true}
              (send-command applicant {:type :application.command/save-draft
                                       :application-id app-id
                                       :field-values [{:form form-id
                                                       :field "fld1"
-                                                      :value (str (::applicant-att-id @test-context))}]})))
+                                                      :value (str (@test-context applicant))}]})))
       (is (= {:success true}
              (send-command applicant {:type :application.command/submit
                                       :application-id app-id})
-             (send-command handler-id {:type :application.command/request-review
-                                       :application-id app-id
-                                       :reviewers [reviewer]
-                                       :comment ""})
-             (send-command handler-id {:type :application.command/request-decision
-                                       :application-id app-id
-                                       :deciders [decider]
-                                       :comment ""})))
-      (swap! test-context assoc ::handler-att-id (upload-api-attachment-ok app-id handler-id "remark.txt"))
-      (swap! test-context assoc ::reviewer-att-id (upload-api-attachment-ok app-id reviewer "remark.txt"))
-      (swap! test-context assoc ::decider-att-id (upload-api-attachment-ok app-id decider "remark.txt"))
+             (send-command integration {:type :application.command/request-review
+                                        :application-id app-id
+                                        :reviewers [reviewer]
+                                        :comment ""})
+             (send-command integration {:type :application.command/request-decision
+                                        :application-id app-id
+                                        :deciders [decider]
+                                        :comment ""})))
+      (swap! test-context assoc handler1 (upload-api-attachment-ok app-id handler1 "remark.txt"))
+      (swap! test-context assoc integration (upload-api-attachment-ok app-id integration "remark.txt"))
+      (swap! test-context assoc reviewer (upload-api-attachment-ok app-id reviewer "remark.txt"))
+      (swap! test-context assoc decider (upload-api-attachment-ok app-id decider "remark.txt"))
       (is (= {:success true}
-             (send-command handler-id {:type :application.command/remark
-                                       :public true
-                                       :application-id app-id
-                                       :comment ""
-                                       :attachments [{:attachment/id (::handler-att-id @test-context)}]})
+             (send-command integration {:type :application.command/remark
+                                        :public true
+                                        :application-id app-id
+                                        :comment ""
+                                        :attachments [{:attachment/id (@test-context integration)}]})
+             (send-command handler1 {:type :application.command/remark
+                                     :public true
+                                     :application-id app-id
+                                     :comment ""
+                                     :attachments [{:attachment/id (@test-context handler1)}]})
              (send-command reviewer {:type :application.command/remark
                                      :public true
                                      :application-id app-id
                                      :comment ""
-                                     :attachments [{:attachment/id (::reviewer-att-id @test-context)}]})
+                                     :attachments [{:attachment/id (@test-context reviewer)}]})
              (send-command decider {:type :application.command/remark
                                     :public true
                                     :application-id app-id
                                     :comment ""
-                                    :attachments [{:attachment/id (::decider-att-id @test-context)}]})))
+                                    :attachments [{:attachment/id (@test-context decider)}]})))
       (is (= {:headers {"Content-Disposition" "attachment;filename=\"remark.txt\""}
               :body "hello from file\n"}
-             (download-api-attachment (::handler-att-id @test-context) handler-id)))
+             (download-api-attachment (@test-context handler1) handler1)))
       (is (= {:headers {"Content-Disposition" "attachment;filename=\"remark (1).txt\""}
               :body "hello from file\n"}
-             (download-api-attachment (::reviewer-att-id @test-context) handler-id)))
+             (download-api-attachment (@test-context integration) handler1)))
       (is (= {:headers {"Content-Disposition" "attachment;filename=\"remark (2).txt\""}
               :body "hello from file\n"}
-             (download-api-attachment (::decider-att-id @test-context) handler-id))))
-
-    (testing "applicant does not have redact permissions"
-      (let [application (get-application-for-user app-id applicant)
-            attachments (sort-by :attachment/id (:application/attachments application))]
-        (is (= 4 (count attachments)))
-        (is (nil? (get-in (first attachments) [:attachment/event :event/id]))
-            "form attachment does not have event id")
-        (is (every? some? (map #(get-in % [:attachment/event :event/id]) (rest attachments)))
-            "other attachments have event ids")
-        (is (not-any? #{"application.command/redact-attachments"} (:application/permissions application)))
-        (is (every? false? (map :attachment/can-redact attachments)))))
-
-    (testing "handler, reviewer and decider permissions"
-      (doseq [user-id #{handler-id reviewer decider}
-              :let [permissions (:application/permissions (get-application-for-user app-id user-id))]]
-        (is (some #{"application.command/redact-attachments"} permissions))))
-
-    (let [applicant-att-id (::applicant-att-id @test-context)
-          handler-att-id (::handler-att-id @test-context)
-          reviewer-att-id (::reviewer-att-id @test-context)
-          decider-att-id (::decider-att-id @test-context)]
-
-      (testing "attachment redact permissions"
-        (is (= [{:attachment/id applicant-att-id :attachment/can-redact false}
-                {:attachment/id handler-att-id :attachment/can-redact true}
-                {:attachment/id reviewer-att-id :attachment/can-redact true}
-                {:attachment/id decider-att-id :attachment/can-redact false}]
-               (->> (get-api-attachments app-id handler-id)
-                    (map #(select-keys % [:attachment/id :attachment/can-redact])))))
-        (is (= [{:attachment/id applicant-att-id :attachment/can-redact false}
-                {:attachment/id handler-att-id :attachment/can-redact false}
-                {:attachment/id reviewer-att-id :attachment/can-redact true}
-                {:attachment/id decider-att-id :attachment/can-redact false}]
-               (->> (get-api-attachments app-id reviewer)
-                    (map #(select-keys % [:attachment/id :attachment/can-redact])))))
-        (is (= [{:attachment/id applicant-att-id :attachment/can-redact false}
-                {:attachment/id handler-att-id :attachment/can-redact false}
-                {:attachment/id reviewer-att-id :attachment/can-redact false}
-                {:attachment/id decider-att-id :attachment/can-redact true}]
-               (->> (get-api-attachments app-id decider)
-                    (map #(select-keys % [:attachment/id :attachment/can-redact]))))))
-
-      (testing "handler tries to redact attachments but fails"
-        (is (= {:success false
-                :errors [{:type "forbidden-redact-attachments"
-                          :attachments [applicant-att-id decider-att-id]}]}
-               (send-command handler-id {:type :application.command/redact-attachments
-                                         :application-id app-id
-                                         :comment ""
-                                         :public true
-                                         :redacted-attachments [{:attachment/id applicant-att-id}
-                                                                {:attachment/id decider-att-id}]
-                                         :attachments []}))))
+             (download-api-attachment (@test-context reviewer) handler1)))
+      (is (= {:headers {"Content-Disposition" "attachment;filename=\"remark (3).txt\""}
+              :body "hello from file\n"}
+             (download-api-attachment (@test-context decider) handler1))))
+
+    (testing "application permissions"
+      (let [get-permissions #(:application/permissions (get-application-for-user app-id %))]
+        (is (not-any? #{"application.command/redact-attachments"} (get-permissions applicant)))
+        (is (some #{"application.command/redact-attachments"} (get-permissions handler1)))
+        (is (some #{"application.command/redact-attachments"} (get-permissions integration)))
+        (is (some #{"application.command/redact-attachments"} (get-permissions reviewer)))
+        (is (some #{"application.command/redact-attachments"} (get-permissions decider)))))
+
+    (testing "attachment redact permissions"
+      (is (= [{:attachment/id (@test-context applicant) :attachment/can-redact false}
+              {:attachment/id (@test-context handler1) :attachment/can-redact false}
+              {:attachment/id (@test-context integration) :attachment/can-redact false}
+              {:attachment/id (@test-context reviewer) :attachment/can-redact false}
+              {:attachment/id (@test-context decider) :attachment/can-redact false}]
+             (->> (get-api-attachments app-id applicant)
+                  (map #(select-keys % [:attachment/id :attachment/can-redact]))))
+          "applicant should not have redact permission for any attachment")
+
+      (is (= [{:attachment/id (@test-context applicant) :attachment/can-redact false}
+              {:attachment/id (@test-context handler1) :attachment/can-redact true}
+              {:attachment/id (@test-context integration) :attachment/can-redact true}
+              {:attachment/id (@test-context reviewer) :attachment/can-redact true}
+              {:attachment/id (@test-context decider) :attachment/can-redact true}]
+             (->> (get-api-attachments app-id handler1)
+                  (map #(select-keys % [:attachment/id :attachment/can-redact]))))
+          "handler should have redact permission for all handling user attachments")
+
+      (is (= [{:attachment/id (@test-context applicant) :attachment/can-redact false}
+              {:attachment/id (@test-context handler1) :attachment/can-redact false}
+              {:attachment/id (@test-context integration) :attachment/can-redact false}
+              {:attachment/id (@test-context reviewer) :attachment/can-redact true}
+              {:attachment/id (@test-context decider) :attachment/can-redact false}]
+             (->> (get-api-attachments app-id reviewer)
+                  (map #(select-keys % [:attachment/id :attachment/can-redact]))))
+          "reviewer should have redact permission only for reviewer attachment")
+
+      (is (= [{:attachment/id (@test-context applicant) :attachment/can-redact false}
+              {:attachment/id (@test-context handler1) :attachment/can-redact false}
+              {:attachment/id (@test-context integration) :attachment/can-redact false}
+              {:attachment/id (@test-context reviewer) :attachment/can-redact false}
+              {:attachment/id (@test-context decider) :attachment/can-redact true}]
+             (->> (get-api-attachments app-id decider)
+                  (map #(select-keys % [:attachment/id :attachment/can-redact]))))
+          "decider should have redact permission only for decider attachment"))
 
-      (testing "reviewer redacts attachment"
-        (is (= {:success true}
-               (send-command reviewer {:type :application.command/redact-attachments
-                                       :application-id app-id
-                                       :comment ""
-                                       :public true
-                                       :redacted-attachments [{:attachment/id reviewer-att-id}]
-                                       :attachments []})))
-        (is (= {:headers {"Content-Disposition" "attachment;filename=\"remark (1).txt\""}
-                :body ""}
-               (download-api-attachment reviewer-att-id handler-id)
-               (download-api-attachment reviewer-att-id reviewer)))
-        (is (= {:headers {"Content-Disposition" (str "attachment;filename=\"redacted_" reviewer-att-id ".txt\"")}
-                :body ""}
-               (download-api-attachment reviewer-att-id applicant)
-               (download-api-attachment reviewer-att-id decider)))
-        (is (= [{:attachment/id applicant-att-id :attachment/can-redact false}
-                {:attachment/id handler-att-id :attachment/can-redact false}
-                {:attachment/id reviewer-att-id :attachment/redacted true}
-                {:attachment/id decider-att-id :attachment/can-redact false}]
-               (->> (get-api-attachments app-id reviewer)
-                    (map #(select-keys % [:attachment/id :attachment/can-redact :attachment/redacted]))))
-            "redacted attachment is no longer redactable")
-        (is (= {:success false
-                :errors [{:type "forbidden-redact-attachments"
-                          :attachments [reviewer-att-id]}]}
-               (send-command handler-id {:type :application.command/redact-attachments
+    (is (= {:success true}
+           (send-command reviewer {:type :application.command/redact-attachments
+                                   :application-id app-id
+                                   :comment ""
+                                   :public true
+                                   :redacted-attachments [{:attachment/id (@test-context reviewer)}]
+                                   :attachments []}))
+        "reviewer can redact attachment")
+
+    (doseq [user [applicant handler1 integration reviewer decider "reporter"]]
+      (testing (str "redacted attachment permissions as " user)
+        (is (= [{:attachment/id (@test-context reviewer) :attachment/redacted true}]
+               (->> (get-api-attachments app-id user)
+                    (filter #(= (@test-context reviewer) (:attachment/id %)))
+                    (map #(select-keys % [:attachment/id :attachment/can-redact :attachment/redacted])))))))
+
+    (testing "downloading redacted attachment as handling user"
+      (is (= {:headers {"Content-Disposition" "attachment;filename=\"remark (2).txt\""}
+              :body ""}
+             (download-api-attachment (@test-context reviewer) handler1)
+             (download-api-attachment (@test-context reviewer) integration)
+             (download-api-attachment (@test-context reviewer) reviewer)
+             (download-api-attachment (@test-context reviewer) decider)
+             (download-api-attachment (@test-context reviewer) "reporter"))))
+
+    (testing "downloading redacted attachment as applying user"
+      (is (= {:headers {"Content-Disposition" (str "attachment;filename=\"redacted_" (@test-context reviewer) ".txt\"")}
+              :body ""}
+             (download-api-attachment (@test-context reviewer) applicant))))
+
+    (testing "redacted attachment cannot be redacted again"
+      (is (= {:success false
+              :errors [{:type "forbidden-redact-attachments"
+                        :attachments [(@test-context reviewer)]}]}
+             (send-command handler1 {:type :application.command/redact-attachments
+                                     :application-id app-id
+                                     :comment ""
+                                     :public true
+                                     :redacted-attachments [{:attachment/id (@test-context reviewer)}]
+                                     :attachments []}))))
+
+    (testing "handler cannot redact applicant attachment"
+      (is (= {:success false
+              :errors [{:type "forbidden-redact-attachments"
+                        :attachments [(@test-context applicant)]}]}
+             (send-command handler1 {:type :application.command/redact-attachments
+                                     :application-id app-id
+                                     :comment ""
+                                     :public true
+                                     :redacted-attachments [{:attachment/id (@test-context applicant)}]
+                                     :attachments []}))))
+
+    (testing "handler can redact other handlers attachment"
+      (is (= {:success true}
+             (send-command handler1 {:type :application.command/redact-attachments
+                                     :application-id app-id
+                                     :comment ""
+                                     :public false
+                                     :redacted-attachments [{:attachment/id (@test-context integration)}]
+                                     :attachments []}))))
+
+    (testing "handler cannot redact decider attachment in decider workflow"
+      (let [workflow-id (test-helpers/create-workflow! {:type :workflow/decider
+                                                        :handlers [handler1 integration]})
+            cat-item-id (test-helpers/create-catalogue-item! {:workflow-id workflow-id
+                                                              :form-id form-id})
+            app-id (test-helpers/create-application! {:catalogue-item-ids [cat-item-id]
+                                                      :actor applicant})
+            decider-test-context (atom nil)]
+        (testing "setup test data"
+          (swap! decider-test-context assoc applicant (upload-api-attachment-ok app-id applicant "applicant.txt"))
+          (is (= {:success true}
+                 (send-command applicant {:type :application.command/save-draft
+                                          :application-id app-id
+                                          :field-values [{:form form-id
+                                                          :field "fld1"
+                                                          :value (str (@decider-test-context applicant))}]})
+                 (send-command applicant {:type :application.command/submit
+                                          :application-id app-id})
+                 (send-command integration {:type :application.command/request-decision
+                                            :application-id app-id
+                                            :deciders [decider]
+                                            :comment "please decide"}))))
+        (testing "decider can approve with comment and attachment"
+          (swap! decider-test-context assoc decider (upload-api-attachment-ok app-id decider "decision.txt"))
+          (is (= {:success true}
+                 (send-command decider {:type :application.command/approve
+                                        :application-id app-id
+                                        :comment "here is my decision document"
+                                        :attachments [{:attachment/id (@decider-test-context decider)}]}))))
+        (testing "handler tries to redact decider attachment but fails"
+          (is (= {:success false
+                  :errors [{:type "forbidden-redact-attachments"
+                            :attachments [(@decider-test-context decider)]}]}
+                 (send-command handler1 {:type :application.command/redact-attachments
                                          :application-id app-id
                                          :comment ""
-                                         :public true
-                                         :redacted-attachments [{:attachment/id reviewer-att-id}]
-                                         :attachments []}))
-            "cannot redact already redacted attachment")))))
+                                         :public false
+                                         :redacted-attachments [{:attachment/id (@decider-test-context decider)}]
+                                         :attachments []}))))))))
 
 (deftest test-application-attachment-zip
   (let [api-key +test-api-key+
diff --git a/test/clj/rems/application/test_model.clj b/test/clj/rems/application/test_model.clj
index d9737101a..79e89568a 100644
--- a/test/clj/rems/application/test_model.clj
+++ b/test/clj/rems/application/test_model.clj
@@ -1799,4 +1799,4 @@
             {:attachment/id 5 :attachment/filename "5.txt"}
             {:attachment/id 6 :attachment/filename "6.txt"}]
            (:application/attachments
-            (#'model/hide-attachments application))))))
+            (#'model/hide-non-accessible-attachments application))))))
diff --git a/test/clj/rems/browser_test_util.clj b/test/clj/rems/browser_test_util.clj
index 95e8fd0dd..adfb6ff99 100644
--- a/test/clj/rems/browser_test_util.clj
+++ b/test/clj/rems/browser_test_util.clj
@@ -48,13 +48,15 @@
   (ensure-empty-directory! (:accessibility-report-dir @test-context))
   (ensure-empty-directory! (:download-dir @test-context)))
 
-(defn downloaded-files [name-or-regex]
-  (if (string? name-or-regex)
-    (let [f (io/file (:download-dir @test-context) name-or-regex)]
-      (when (.exists f) [f]))
-    (for [file (.listFiles (:download-dir @test-context))
-          :when (re-matches name-or-regex (.getName file))]
-      file)))
+(defn downloaded-files
+  ([] (for [file (.listFiles (:download-dir @test-context))]
+        file))
+  ([name-or-regex] (if (string? name-or-regex)
+                     (let [f (io/file (:download-dir @test-context) name-or-regex)]
+                       (when (.exists f) [f]))
+                     (for [file (.listFiles (:download-dir @test-context))
+                           :when (re-matches name-or-regex (.getName file))]
+                       file))))
 
 (defn delete-downloaded-files! [name-or-regex]
   (let [files (if (string? name-or-regex)
@@ -486,7 +488,9 @@
   (scroll-and-click [{:css (str "input[value='" value "']")}]))
 
 (defn wait-for-downloads [string-or-regex]
-  (wait-predicate #(seq (downloaded-files string-or-regex))))
+  (wait-predicate #(seq (downloaded-files string-or-regex))
+                  #(do {:string-or-regex string-or-regex
+                        :downloaded-files (downloaded-files)})))
 
 (defn value-of-el
   "Return the \"value\" an element `el`.
diff --git a/test/clj/rems/test_browser.clj b/test/clj/rems/test_browser.clj
index 51075204c..e25aecd48 100644
--- a/test/clj/rems/test_browser.clj
+++ b/test/clj/rems/test_browser.clj
@@ -491,18 +491,25 @@
 
         (testing "upload three attachments, then remove one"
           (btu/upload-file attachment-field-upload-selector "test-data/test.txt")
-          (btu/wait-predicate #(= ["Download file\ntest.txt"]
+          (btu/wait-predicate #(= (->> ["test.txt"]
+                                       (mapv (fn [filename]
+                                               (str/join "\n" (list "Download file" filename "Remove")))))
                                   (get-application-attachments))
                               #(do {:attachments (get-application-attachments)}))
           (btu/upload-file attachment-field-upload-selector "test-data/test-fi.txt")
-          (btu/wait-predicate #(= ["Download file\ntest.txt" "Download file\ntest-fi.txt"]
+          (btu/wait-predicate #(= (->> ["test.txt" "test-fi.txt"]
+                                       (mapv (fn [filename]
+                                               (str/join "\n" (list "Download file" filename "Remove")))))
                                   (get-application-attachments))
                               #(do {:attachments (get-application-attachments)}))
           (btu/upload-file attachment-field-upload-selector "test-data/test-sv.txt")
-          (btu/wait-predicate #(= ["Download file\ntest.txt" "Download file\ntest-fi.txt" "Download file\ntest-sv.txt"]
+          (btu/wait-predicate #(= (->> ["test.txt" "test-fi.txt" "test-sv.txt"]
+                                       (mapv (fn [filename]
+                                               (str/join "\n" (list "Download file" filename "Remove")))))
                                   (get-application-attachments))
                               #(do {:attachments (get-application-attachments)}))
-          (btu/scroll-and-click-el (last (btu/query-all {:css (str "button.remove-attachment-" attachment-field-id)}))))
+          (btu/scroll-and-click-el (last (btu/query-all [{:css (str "#container-" attachment-field-id)}
+                                                         {:css ".attachments button.remove"}]))))
 
         (testing "uploading oversized attachment should display error"
           (btu/with-client-config {:attachment-max-size 900}
@@ -550,7 +557,7 @@
 
         (fill-form-field "Simple text field" "Private field answer before autosave")
 
-        (btu/with-client-config {:enable-autosave false} ; when testing locally
+        (btu/with-client-config {:enable-autosave false}
           (testing "save draft succesfully, but show validation warnings"
             (fill-form-field "Email field" "user")
             (btu/scroll-and-click :save)
@@ -587,10 +594,12 @@
         (btu/gather-axe-results "accepted-licenses")
 
         (testing "attachment download"
-          (btu/scroll-and-click [{:css ".attachment-link" :fn/text "test.txt"}])
-          (btu/wait-for-downloads "test.txt")
-          (is (= (slurp "test-data/test.txt")
-                 (slurp (first (btu/downloaded-files "test.txt"))))))
+          (let [download-button (->> (btu/query-all {:css ".attachment-link .download"})
+                                     (find-first (comp #{"Download file\ntest.txt"} btu/value-of-el)))]
+            (btu/scroll-and-click-el download-button)
+            (btu/wait-for-downloads "test.txt")
+            (is (= (slurp "test-data/test.txt")
+                   (slurp (first (btu/downloaded-files "test.txt")))))))
 
         (send-application)
         (btu/gather-axe-results "sent-application")
@@ -876,19 +885,25 @@
       (is (btu/eventually-visible? :comment-approve-reject))
       (btu/fill-human :comment-approve-reject "this is a comment")
       (btu/upload-file :upload-approve-reject-input "test-data/test.txt")
-      (is (btu/eventually-visible? [{:css "a.attachment-link"}]))
+      (is (btu/eventually-visible? [{:css ".attachment-link"}]))
       (btu/upload-file :upload-approve-reject-input "test-data/test-fi.txt")
-      (btu/wait-predicate #(= ["Download file\ntest.txt" "Download file\ntest-fi.txt"]
+      (btu/wait-predicate #(= (->> ["test.txt" "test-fi.txt"]
+                                   (mapv (fn [filename]
+                                           (str/join "\n" (list "Download file" filename "Remove")))))
                               (get-attachments :actions-approve-reject))
                           #(do {:attachments (get-attachments :actions-approve-reject)})))
     (testing "add and remove a third attachment"
       (btu/upload-file :upload-approve-reject-input "resources/public/img/rems_logo_en.png")
-      (btu/wait-predicate #(= ["Download file\ntest.txt" "Download file\ntest-fi.txt" "Download file\nrems_logo_en.png"]
+      (btu/wait-predicate #(= (->> ["test.txt" "test-fi.txt" "rems_logo_en.png"]
+                                   (mapv (fn [filename]
+                                           (str/join "\n" (list "Download file" filename "Remove")))))
                               (get-attachments :actions-approve-reject))
                           #(do {:attachments (get-attachments :actions-approve-reject)}))
-      (let [buttons (btu/query-all {:css "button.remove-attachment-approve-reject"})]
+      (let [buttons (btu/query-all [:actions-approve-reject {:css ".attachments button.remove"}])]
         (btu/click-el (last buttons)))
-      (btu/wait-predicate #(= ["Download file\ntest.txt" "Download file\ntest-fi.txt"]
+      (btu/wait-predicate #(= (->> ["test.txt" "test-fi.txt"]
+                                   (mapv (fn [filename]
+                                           (str/join "\n" (list "Download file" filename "Remove")))))
                               (get-attachments :actions-approve-reject))
                           #(do {:attachments (get-attachments :actions-approve-reject)})))
     (testing "approve"
@@ -897,7 +912,9 @@
     (testing "event visible in eventlog"
       (is (btu/visible? {:css "div.event-description b" :fn/text "Developer approved the application."})))
     (testing "attachments visible in eventlog"
-      (is (= ["Download file\ntest.txt" "Download file\ntest-fi.txt"]
+      (is (= (->> ["test.txt" "test-fi.txt"]
+                  (mapv (fn [filename]
+                          (str/join "\n" (list "Download file" filename)))))
              (get-application-event-attachments))))
 
     (testing "event via api"