Repository: CSCfi/rems
Pull request #1147 (2019-04-18 10:53:20)

Update page title when language changes
#1141 See also rems-deploy pull request: https://github.com/CSCfi/rems-deploy/pull/32


Patch:
diff --git a/example-theme/extra-translations/fi.edn b/example-theme/extra-translations/fi.edn
index cd331eae9..5840efa26 100644
--- a/example-theme/extra-translations/fi.edn
+++ b/example-theme/extra-translations/fi.edn
@@ -3,9 +3,12 @@
   :administration {:catalogue-item "Kielivara"
                    :catalogue-items "Kielivarat"
                    :create-catalogue-item "Uusi kielivara"}
+  :applications {:resource "Kielivara"}
   :cart {:apply-for-bundle "Hae näitä kielivaroja (%1 kpl) yhdessä "
          :header "%1 kielivaraa korissa"}
   :catalogue {:catalogue "Kielivarat"
               :header "Kielivarat"
               :apply-resources "Hae uusia kielivaroja"}
-  :navigation {:catalogue "Kielivarat"}}}
\ No newline at end of file
+  :form {:back "Takaisin kielivaroihin"}}
+  :navigation {:catalogue "Kielivarat"
+               :create-catalogue-item "Luo kielivara"}}
\ No newline at end of file
diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql
index 55a5b873f..f901d3aeb 100644
--- a/resources/sql/queries.sql
+++ b/resources/sql/queries.sql
@@ -475,14 +475,9 @@ VALUES
  /*~ (if (:workflow params) */ :workflow::jsonb /*~*/ NULL /*~ ) ~*/
 );
 
--- :name update-workflow! :!
+-- :name set-workflow-state! :!
 UPDATE workflow
-SET
-  id = :id
-  --~(when (contains? params :enabled) ", enabled = :enabled")
-  --~(when (contains? params :archived) ", archived = :archived")
-  --~(when (contains? params :title) ", title = :title")
-  --~(when (contains? params :workflow) ", workflowBody = :workflow::jsonb")
+SET (enabled, archived) = (:enabled, :archived)
 WHERE id = :id
 
 -- :name create-workflow-license! :insert
diff --git a/src/clj/rems/api/applications_v2.clj b/src/clj/rems/api/applications_v2.clj
index a11a5cb4a..021f12331 100644
--- a/src/clj/rems/api/applications_v2.clj
+++ b/src/clj/rems/api/applications_v2.clj
@@ -1,7 +1,5 @@
 (ns rems.api.applications-v2
   (:require [clojure.core.cache :as cache]
-            [clojure.set :as set]
-            [clojure.test :refer [deftest is testing]]
             [medley.core :refer [map-vals]]
             [mount.core :as mount]
             [rems.application.events-cache :as events-cache]
@@ -9,21 +7,40 @@
             [rems.auth.util :refer [throw-forbidden]]
             [rems.db.applications :as applications]
             [rems.db.catalogue :as catalogue]
+            [rems.db.core :as db]
+            [rems.db.form :as form]
             [rems.db.licenses :as licenses]
             [rems.db.users :as users]
-            [rems.db.workflow :as workflow]
             [rems.permissions :as permissions]
             [rems.scheduler :as scheduler]
             [rems.util :refer [getx atom?]])
   (:import [org.joda.time Duration]))
 
-(def ^:private injections
-  {:get-form applications/get-form
-   :get-catalogue-item catalogue/get-localized-catalogue-item
-   :get-license licenses/get-license
-   :get-user users/get-user-attributes
-   :get-users-with-role users/get-users-with-role
-   :get-workflow workflow/get-workflow})
+(defn- get-form [form-id]
+  (-> (form/get-form form-id)
+      (select-keys [:id :organization :title :start :end])
+      (assoc :items (->> (db/get-form-items {:id form-id})
+                         (mapv #(applications/process-field nil form-id %))))))
+
+(defn- get-catalogue-item [catalogue-item-id]
+  (assert (int? catalogue-item-id)
+          (pr-str catalogue-item-id))
+  (catalogue/get-localized-catalogue-item catalogue-item-id))
+
+(defn- get-license [license-id]
+  (licenses/get-license license-id))
+
+(defn- get-user [user-id]
+  (users/get-user-attributes user-id))
+
+(defn- get-users-with-role [role]
+  (users/get-users-with-role role))
+
+(def ^:private injections {:get-form get-form
+                           :get-catalogue-item get-catalogue-item
+                           :get-license get-license
+                           :get-user get-user
+                           :get-users-with-role get-users-with-role})
 
 ;; short-lived cache to speed up pollers which get the application
 ;; repeatedly for each event instead of building their own projection
@@ -79,10 +96,7 @@
             it creates a cache invalidation problem here.
           ::apps-by-user
           - Map from user ID to a list of applications which the user can see.
-            Built from the enriched apps by calling `apply-user-permissions`.
-          ::roles-by-user
-          - Map from user ID to a set of all application roles which the user has,
-            a union of roles from all applications."}
+            Built from the enriched apps by calling `apply-user-permissions`."}
   all-applications-cache
   :start (events-cache/new))
 
@@ -96,36 +110,7 @@
                  (update apps-by-user user conj app))
                {})))
 
-(deftest test-group-apps-by-user
-  (let [apps [(-> {:application/id 1}
-                  (permissions/give-role-to-users :foo ["user-1" "user-2"]))
-              (-> {:application/id 2}
-                  (permissions/give-role-to-users :bar ["user-1"]))]]
-    (is (= {"user-1" [{:application/id 1} {:application/id 2}]
-            "user-2" [{:application/id 1}]}
-           (->> (group-apps-by-user apps)
-                (map-vals (fn [apps]
-                            (->> apps
-                                 (map #(select-keys % [:application/id]))
-                                 (sort-by :application/id)))))))))
-
-(defn- group-roles-by-user [apps]
-  (->> apps
-       (mapcat (fn [app] (::permissions/user-roles app)))
-       (reduce (fn [roles-by-user [user roles]]
-                 (update roles-by-user user set/union roles))
-               {})))
-
-(deftest test-group-roles-by-user
-  (let [apps [(-> {:application/id 1}
-                  (permissions/give-role-to-users :foo ["user-1" "user-2"]))
-              (-> {:application/id 2}
-                  (permissions/give-role-to-users :bar ["user-1"]))]]
-    (is (= {"user-1" #{:foo :bar}
-            "user-2" #{:foo}}
-           (group-roles-by-user apps)))))
-
-(defn refresh-all-applications-cache! []
+(defn- refresh-all-applications-cache! []
   (events-cache/refresh!
    all-applications-cache
    (fn [state events]
@@ -143,8 +128,7 @@
                               (merge (::enriched-apps state)))]
        {::raw-apps raw-apps
         ::enriched-apps enriched-apps
-        ::apps-by-user (group-apps-by-user (vals enriched-apps))
-        ::roles-by-user (group-roles-by-user (vals enriched-apps))}))))
+        ::apps-by-user (group-apps-by-user (vals enriched-apps))}))))
 
 (defn get-all-unrestricted-applications []
   (-> (refresh-all-applications-cache!)
@@ -156,11 +140,6 @@
       (get-in [::apps-by-user user-id])
       (->> (map exclude-unnecessary-keys-from-overview))))
 
-(defn get-all-application-roles [user-id]
-  (-> (refresh-all-applications-cache!)
-      (get-in [::roles-by-user user-id])
-      (set)))
-
 (defn- own-application? [application]
   (some #{:applicant :member} (:application/roles application)))
 
diff --git a/src/clj/rems/api/workflows.clj b/src/clj/rems/api/workflows.clj
index 361fb1e85..2e5dba8db 100644
--- a/src/clj/rems/api/workflows.clj
+++ b/src/clj/rems/api/workflows.clj
@@ -3,7 +3,7 @@
             [clojure.test :refer [deftest is]]
             [compojure.api.sweet :refer :all]
             [rems.api.applications :refer [User get-users]]
-            [rems.api.schema :refer [SuccessResponse]]
+            [rems.api.schema :refer [SuccessResponse UpdateStateCommand]]
             [rems.api.util]
             [rems.db.core :as db]
             [rems.db.workflow :as workflow]
@@ -83,14 +83,6 @@
    :type s/Keyword
    (s/optional-key :handlers) [UserId]})
 
-(s/defschema UpdateWorkflowCommand
-  {:id s/Num
-   (s/optional-key :title) s/Str
-   (s/optional-key :handlers) [UserId]
-   ;; type can't change
-   (s/optional-key :enabled) s/Bool
-   (s/optional-key :archived) s/Bool})
-
 (s/defschema CreateWorkflowResponse
   {:success s/Bool
    :id s/Num})
@@ -139,7 +131,7 @@
     (PUT "/update" []
       :summary "Update workflow"
       :roles #{:owner}
-      :body [command UpdateWorkflowCommand]
+      :body [command UpdateStateCommand]
       :return SuccessResponse
       (ok (workflow/update-workflow! command)))
 
diff --git a/src/clj/rems/application/events.clj b/src/clj/rems/application/events.clj
index 17d161755..aa8150777 100644
--- a/src/clj/rems/application/events.clj
+++ b/src/clj/rems/application/events.clj
@@ -40,13 +40,15 @@
 (s/defschema CreatedEvent
   (assoc EventBase
          :event/type (s/enum :application.event/created)
-         :application/external-id (s/maybe s/Str)
          :application/resources [{:catalogue-item/id s/Int
                                   :resource/ext-id s/Str}]
          :application/licenses [{:license/id s/Int}]
          :form/id s/Int
          :workflow/id s/Int
-         :workflow/type s/Keyword))
+         :workflow/type s/Keyword
+         :application/external-id (s/maybe s/Str)
+         ;; workflow-specific data
+         (s/optional-key :workflow.dynamic/handlers) #{s/Str}))
 (s/defschema DecidedEvent
   (assoc EventBase
          :event/type (s/enum :application.event/decided)
diff --git a/src/clj/rems/application/model.clj b/src/clj/rems/application/model.clj
index bcfbd474c..e1043afcb 100644
--- a/src/clj/rems/application/model.clj
+++ b/src/clj/rems/application/model.clj
@@ -82,6 +82,7 @@
   [application event]
   (-> application
       (permissions/give-role-to-users :applicant [(:event/actor event)])
+      (permissions/give-role-to-users :handler (:workflow.dynamic/handlers event))
       (permissions/set-role-permissions draft-permissions)))
 
 (defmethod calculate-permissions :application.event/member-added
@@ -185,6 +186,7 @@
                                     :workflow/type (:workflow/type event)
                                     ;; TODO: other workflows
                                     ;; TODO: extract an event handler for dynamic workflow specific stuff
+                                    :workflow.dynamic/handlers (:workflow.dynamic/handlers event)
                                     :workflow.dynamic/awaiting-commenters #{}
                                     :workflow.dynamic/awaiting-deciders #{}})))
 
@@ -470,21 +472,11 @@
             :application/members
             enrich-members)))
 
-(defn enrich-workflow-handlers [application get-workflow]
-  (if (= :workflow/dynamic (get-in application [:application/workflow :workflow/type]))
-    (let [workflow (get-workflow (get-in application [:application/workflow :workflow/id]))
-          handlers (set (get-in workflow [:workflow :handlers]))]
-      (-> application
-          (assoc-in [:application/workflow :workflow.dynamic/handlers] handlers)
-          (permissions/give-role-to-users :handler handlers)))
-    application))
-
 (defn- enrich-super-users [application get-users-with-role]
   (-> application
       (permissions/give-role-to-users :reporter (get-users-with-role :reporter))))
 
-(defn enrich-with-injections [application {:keys [get-form get-catalogue-item get-license
-                                                  get-user get-users-with-role get-workflow]}]
+(defn enrich-with-injections [application {:keys [get-form get-catalogue-item get-license get-user get-users-with-role]}]
   (let [answer-versions (remove nil? [(::draft-answers application)
                                       (::submitted-answers application)
                                       (::previous-submitted-answers application)])
@@ -507,7 +499,6 @@
         (update :application/licenses enrich-licenses get-license)
         (assoc :application/applicant-attributes (get-user (:application/applicant application)))
         (enrich-user-attributes get-user)
-        (enrich-workflow-handlers get-workflow)
         (enrich-super-users get-users-with-role))))
 
 (defn build-application-view [events injections]
@@ -524,6 +515,10 @@
   [event]
   event)
 
+(defmethod hide-sensitive-event-content :application.event/created
+  [event]
+  (dissoc event :workflow.dynamic/handlers))
+
 (defmethod hide-sensitive-event-content :application.event/member-invited
   [event]
   (dissoc event :invitation/token))
diff --git a/src/clj/rems/db/applications.clj b/src/clj/rems/db/applications.clj
index c326ae918..0b0ce982a 100644
--- a/src/clj/rems/db/applications.clj
+++ b/src/clj/rems/db/applications.clj
@@ -706,7 +706,8 @@
                                   licenses)
        :form/id form-id
        :workflow/id workflow-id
-       :workflow/type (:type workflow)})))
+       :workflow/type (:type workflow)
+       :workflow.dynamic/handlers (set (:handlers workflow))})))
 
 (defn add-application-created-event! [opts]
   (add-event! (application-created-event (assoc opts :allocate-external-id? true))))
@@ -730,7 +731,7 @@
 (defn- valid-user? [userid]
   (not (nil? (users/get-user-attributes userid))))
 
-(defn get-form [form-id]
+(defn- get-form [form-id]
   (-> (form/get-form form-id)
       (select-keys [:id :organization :title :start :end])
       (assoc :items (->> (db/get-form-items {:id form-id})
@@ -751,9 +752,7 @@
 (defn command! [cmd]
   (assert (:application-id cmd))
   (let [events (get-application-events (:application-id cmd))
-        app (-> nil
-                (dynamic/apply-events events)
-                (model/enrich-workflow-handlers workflow/get-workflow))
+        app (dynamic/apply-events nil events)
         result (dynamic/handle-command cmd app db-injections)]
     (if (:success result)
       (add-event! (:result result))
diff --git a/src/clj/rems/db/dynamic_roles.clj b/src/clj/rems/db/dynamic_roles.clj
new file mode 100644
index 000000000..5c9227e4e
--- /dev/null
+++ b/src/clj/rems/db/dynamic_roles.clj
@@ -0,0 +1,63 @@
+(ns rems.db.dynamic-roles
+  (:require [clojure.set :as set]
+            [clojure.test :refer [deftest is testing]]
+            [mount.core :as mount]
+            [rems.application.events-cache :as events-cache]
+            [rems.application.model :as model]
+            [rems.permissions :as permissions]))
+
+(defn- permissions-of-all-applications [applications event]
+  (if-let [app-id (:application/id event)] ; old style events don't have :application/id
+    (update applications app-id model/calculate-permissions event)
+    applications))
+
+(defn- group-roles-by-user [applications-by-id]
+  (->> applications-by-id
+       (vals)
+       (mapcat (fn [app] (::permissions/user-roles app)))
+       (reduce (fn [roles-by-user [user roles]]
+                 (update roles-by-user user set/union roles))
+               {})))
+
+(defn- get-user-roles [user roles-by-user]
+  (get roles-by-user user #{}))
+
+(defn- roles-from-all-applications [user events]
+  (->> (reduce permissions-of-all-applications nil events)
+       (group-roles-by-user)
+       (get-user-roles user)))
+
+(deftest test-roles-from-all-applications
+  (let [events [{:event/type :application.event/created
+                 :event/actor "applicant-only"
+                 :application/id 1
+                 :workflow.dynamic/handlers ["applicant-and-handler"]}
+                {:event/type :application.event/created
+                 :event/actor "applicant-and-handler"
+                 :application/id 2}]]
+    (is (= #{:applicant}
+           (roles-from-all-applications "applicant-only" events)))
+    (is (= #{:applicant :handler}
+           (roles-from-all-applications "applicant-and-handler" events)))
+    (is (= #{}
+           (roles-from-all-applications "unknown" events)))))
+
+(mount/defstate
+  ^{:doc "The cached state will contain the following keys:
+          ::permissions
+          - Map from application ID to the roles and permissions of an application.
+          ::roles-by-user
+          - Map from user ID to a set of all application roles which the user has,
+            a union of roles from all applications."}
+  dynamic-roles-cache
+  :start (events-cache/new))
+
+(defn get-roles [user]
+  (->> (events-cache/refresh!
+        dynamic-roles-cache
+        (fn [state events]
+          (let [permissions (reduce permissions-of-all-applications (::permissions state) events)]
+            {::permissions permissions
+             ::roles-by-user (group-roles-by-user permissions)})))
+       ::roles-by-user
+       (get-user-roles user)))
diff --git a/src/clj/rems/db/workflow.clj b/src/clj/rems/db/workflow.clj
index 92cb45de0..b6e4c57fc 100644
--- a/src/clj/rems/db/workflow.clj
+++ b/src/clj/rems/db/workflow.clj
@@ -62,8 +62,5 @@
       {:success false
        :errors [{:type :t.administration.errors/workflow-in-use :catalogue-items catalogue-items}]}
       (do
-        (db/update-workflow! (merge (select-keys command [:id :enabled :archived :title])
-                                    (when-let [handlers (:handlers command)]
-                                      {:workflow (json/generate-string {:type :workflow/dynamic
-                                                                        :handlers handlers})})))
+        (db/set-workflow-state! command)
         {:success true}))))
diff --git a/src/clj/rems/middleware.clj b/src/clj/rems/middleware.clj
index ace696e2f..b377dd072 100644
--- a/src/clj/rems/middleware.clj
+++ b/src/clj/rems/middleware.clj
@@ -6,11 +6,11 @@
             [clojure.test :refer [deftest is testing]]
             [clojure.tools.logging :as log]
             [clojure.walk :refer [keywordize-keys]]
-            [rems.api.applications-v2 :as applications-v2]
             [rems.auth.auth :as auth]
             [rems.config :refer [env]]
             [rems.context :as context]
             [rems.db.api-key :as api-key]
+            [rems.db.dynamic-roles :as dynamic-roles]
             [rems.db.roles :as roles]
             [rems.env :refer [+defaults+]]
             [rems.layout :refer [error-page]]
@@ -82,7 +82,7 @@
               context/*flash* (:flash request)
               context/*roles* (when context/*user*
                                 (set/union (roles/get-roles (getx-user-id))
-                                           (applications-v2/get-all-application-roles (getx-user-id))))]
+                                           (dynamic-roles/get-roles (getx-user-id))))]
       (with-mdc {:roles (str/join " " (sort context/*roles*))}
         (handler request)))))
 
diff --git a/src/clj/rems/workflow/dynamic.clj b/src/clj/rems/workflow/dynamic.clj
index 6fb66d1dd..effe602a4 100644
--- a/src/clj/rems/workflow/dynamic.clj
+++ b/src/clj/rems/workflow/dynamic.clj
@@ -261,7 +261,8 @@
 (deftest test-handle-command
   (let [application (apply-events nil [{:event/type :application.event/created
                                         :event/actor "applicant"
-                                        :workflow/type :workflow/dynamic}])
+                                        :workflow/type :workflow/dynamic
+                                        :workflow.dynamic/handlers #{"assistant"}}])
         command {:application-id 123 :time (DateTime. 1000)
                  :type :application.command/save-draft
                  :field-values []
diff --git a/src/cljs/rems/administration/components.cljs b/src/cljs/rems/administration/components.cljs
index 308defea9..e798ef08c 100644
--- a/src/cljs/rems/administration/components.cljs
+++ b/src/cljs/rems/administration/components.cljs
@@ -46,6 +46,11 @@
   [context keys]
   (input-field (merge keys {:context context :type "text"})))
 
+(defn number-field
+  "A basic number field, full page width."
+  [context keys]
+  (input-field (merge keys {:context context :type "number"})))
+
 (defn textarea-autosize
   "A basic textarea, full page width."
   [context {:keys [keys label placeholder]}]
diff --git a/src/cljs/rems/administration/create_form.cljs b/src/cljs/rems/administration/create_form.cljs
index 06e50fc92..5ca5c9b1b 100644
--- a/src/cljs/rems/administration/create_form.cljs
+++ b/src/cljs/rems/administration/create_form.cljs
@@ -3,7 +3,7 @@
             [goog.string :refer [parseInt]]
             [re-frame.core :as rf]
             [rems.administration.administration :refer [administration-navigator-container]]
-            [rems.administration.components :refer [checkbox localized-text-field radio-button-group text-field]]
+            [rems.administration.components :refer [checkbox localized-text-field number-field radio-button-group text-field]]
             [rems.administration.items :as items]
             [rems.atoms :refer [enrich-user]]
             [rems.collapsible :as collapsible]
@@ -87,10 +87,6 @@
   (and (not (str/blank? (:key option)))
        (valid-required-localized-string? (:label option) languages)))
 
-(defn- parse-maxlength [maxlength]
-  (let [parsed (parseInt maxlength)]
-    (when-not (js/isNaN parsed) parsed)))
-
 (defn- valid-request-field? [field languages]
   (and (valid-required-localized-string? (:title field) languages)
        (boolean? (:optional field))
@@ -98,9 +94,6 @@
        (if (supports-input-prompt? field)
          (valid-optional-localized-string? (:input-prompt field) languages)
          (nil? (:input-prompt field)))
-       (if (supports-maxlength? field)
-         (not (neg? (:maxlength field)))
-         (nil? (:maxlength field)))
        (if (supports-options? field)
          (every? #(valid-option? % languages) (:options field))
          (nil? (:options field)))))
@@ -121,7 +114,8 @@
          (when (supports-input-prompt? field)
            {:input-prompt (build-localized-string (:input-prompt field) languages)})
          (when (supports-maxlength? field)
-           {:maxlength (parse-maxlength (:maxlength field))})
+           {:maxlength (when-not (str/blank? (:maxlength field))
+                         (parseInt (:maxlength field)))})
          (when (supports-options? field)
            {:options (for [{:keys [key label]} (:options field)]
                        {:key key
@@ -168,8 +162,8 @@
                                  :label (text :t.create-form/input-prompt)}])
 
 (defn- form-field-maxlength-field [field-index]
-  [text-field context {:keys [:fields field-index :maxlength]
-                       :label (text :t.create-form/maxlength)}])
+  [number-field context {:keys [:fields field-index :maxlength]
+                         :label (text :t.create-form/maxlength)}])
 
 (defn- add-form-field-option-button [field-index]
   [:a {:href "#"
@@ -285,7 +279,7 @@
   {:field/type (keyword (:type field))
    :field/title (:title field)
    :field/placeholder (:input-prompt field)
-   :field/max-length (parse-maxlength (:maxlength field))
+   :field/max-length (:maxlength field)
    :field/optional (:optional field)
    :field/options (:options field)})
 
diff --git a/src/cljs/rems/spa.cljs b/src/cljs/rems/spa.cljs
index b6ea0d308..0b7461b02 100644
--- a/src/cljs/rems/spa.cljs
+++ b/src/cljs/rems/spa.cljs
@@ -123,6 +123,7 @@
  (fn [language]
    (let [localized-css (str "/css/" (name language) "/screen.css")]
      (set! (.. js/document -documentElement -lang) language)
+     (set! (.. js/document -title) (text :t.header/title))
      ;; Figwheel replaces the linked stylesheet
      ;; so we need to search dynamically
      (doseq [element (array-seq (.getElementsByTagName js/document "link"))]


Test patch:
diff --git a/test/clj/rems/api/test_workflows.clj b/test/clj/rems/api/test_workflows.clj
index a671b4dd9..6ce3f1ce8 100644
--- a/test/clj/rems/api/test_workflows.clj
+++ b/test/clj/rems/api/test_workflows.clj
@@ -80,72 +80,21 @@
                              :handlers ["bob" "carl"]}
                   :enabled true
                   :archived false}
-                 (select-keys workflow [:id :organization :title :workflow :enabled :archived]))))))))
-
-(deftest workflows-update-test
-  (let [api-key "42"
-        user-id "owner"
-        wf-spec {:organization "abc"
-                 :title "dynamic workflow"
-                 :type :dynamic
-                 :handlers ["bob" "carl"]}
-        wfid (-> (request :post "/api/workflows/create")
-                 (json-body wf-spec)
-                 (authenticate api-key user-id)
-                 handler
-                 read-ok-body
-                 :id)
-        ;; this is a subset of what we expect to get from the api
-        expected {:id wfid
-                  :organization "abc"
-                  :title "dynamic workflow"
-                  :workflow {:type "workflow/dynamic"
-                             :handlers ["bob" "carl"]}
-                  :enabled true
-                  :expired false
-                  :archived false}
-        fetch (fn []
-                (let [wfs (-> (request :get "/api/workflows" {:archived true :expired true :disabled true})
-                              (authenticate api-key user-id)
-                              handler
-                              read-ok-body)]
-                  (select-keys
-                   (first (filter #(= wfid (:id %)) wfs))
-                   (keys expected))))
-        update #(-> (request :put "/api/workflows/update")
-                    (json-body (merge {:id wfid} %))
-                    (authenticate api-key user-id)
-                    handler
-                    read-ok-body)]
-    (testing "before changes"
-      (is (= expected (fetch))))
-    (testing "disable and archive"
-      (is (:success (update {:enabled false :archived true})))
-      (is (= (assoc expected
-                    :enabled false
-                    :archived true)
-             (fetch))))
-    (testing "re-enable"
-      (is (:success (update {:enabled true})))
-      (is (= (assoc expected
-                    :archived true)
-             (fetch))))
-    (testing "unarchive"
-      (is (:success (update {:archived false})))
-      (is (= expected
-             (fetch))))
-    (testing "change title"
-      (is (:success (update {:title "x"})))
-      (is (= (assoc expected
-                    :title "x")
-             (fetch))))
-    (testing "change handlers"
-      (is (:success (update {:handlers ["owner" "alice"]})))
-      (is (= (assoc expected
-                    :title "x"
-                    :workflow {:type "workflow/dynamic"
-                               :handlers ["owner" "alice"]})
-             (fetch))))))
+                 (select-keys workflow [:id :organization :title :workflow :enabled :archived])))))
+      (testing "and update"
+        (-> (request :put "/api/workflows/update")
+            (json-body {:id id :enabled false :archived true})
+            (authenticate "42" "owner")
+            handler
+            assert-response-is-ok)
+        (let [workflows (-> (request :get "/api/workflows" {:disabled true :archived true})
+                            (authenticate "42" "owner")
+                            handler
+                            assert-response-is-ok
+                            read-body)
+              workflow (first (filter #(= id (:id %)) workflows))]
+          (is (= {:id id :enabled false :archived true}
+                 (select-keys workflow [:id :enabled :archived]))))))))
 
 (deftest workflows-api-filtering-test
   (let [unfiltered (-> (request :get "/api/workflows" {:expired true})
diff --git a/test/clj/rems/application/test_model.clj b/test/clj/rems/application/test_model.clj
index 2b61faf71..441f1566a 100644
--- a/test/clj/rems/application/test_model.clj
+++ b/test/clj/rems/application/test_model.clj
@@ -139,30 +139,12 @@
   {:owner ["owner1"]
    :reporter ["reporter1"]})
 
-(def ^:private get-workflow
-  {50 {:id 50
-       :organization "org"
-       :title "workflow title"
-       :workflow {:type "workflow/dynamic"
-                  :handlers ["handler"]}
-       :licenses nil
-       :visibility "private"
-       :fnlround 0
-       :owneruserid "owner"
-       :modifieruserid "owner"
-       :start (DateTime. 100)
-       :end nil
-       :enabled true
-       :archived false
-       :expired false}})
-
 (deftest test-application-view
   (let [injections {:get-form get-form
                     :get-catalogue-item get-catalogue-item
                     :get-license get-license
                     :get-user get-user
-                    :get-users-with-role get-users-with-role
-                    :get-workflow get-workflow}
+                    :get-users-with-role get-users-with-role}
         apply-events (fn [events]
                        (let [application (-> events
                                              events/validate-events
@@ -186,7 +168,8 @@
                                             {:license/id 32}]
                      :form/id 40
                      :workflow/id 50
-                     :workflow/type :workflow/dynamic}]
+                     :workflow/type :workflow/dynamic
+                     :workflow.dynamic/handlers #{"handler"}}]
             expected-application {:application/id 1
                                   :application/external-id "extid"
                                   :application/state :application.state/draft
@@ -665,7 +648,8 @@
   ;; TODO: is this what we want? wouldn't it be useful to be able to write more than one comment?
   (testing "commenter may comment only once"
     (let [requested (reduce model/calculate-permissions nil [{:event/type :application.event/created
-                                                              :event/actor "applicant"}
+                                                              :event/actor "applicant"
+                                                              :workflow.dynamic/handlers ["handler"]}
                                                              {:event/type :application.event/submitted
                                                               :event/actor "applicant"}
                                                              {:event/type :application.event/comment-requested
@@ -682,7 +666,8 @@
 
   (testing "decider may decide only once"
     (let [requested (reduce model/calculate-permissions nil [{:event/type :application.event/created
-                                                              :event/actor "applicant"}
+                                                              :event/actor "applicant"
+                                                              :workflow.dynamic/handlers ["handler"]}
                                                              {:event/type :application.event/submitted
                                                               :event/actor "applicant"}
                                                              {:event/type :application.event/decision-requested
@@ -697,12 +682,14 @@
 
   (testing "everyone can accept invitation"
     (let [created (reduce model/calculate-permissions nil [{:event/type :application.event/created
-                                                            :event/actor "applicant"}])]
+                                                            :event/actor "applicant"
+                                                            :workflow.dynamic/handlers ["handler"]}])]
       (is (= #{:application.command/accept-invitation}
              (permissions/user-permissions created "joe")))))
   (testing "nobody can accept invitation for closed application"
     (let [closed (reduce model/calculate-permissions nil [{:event/type :application.event/created
-                                                           :event/actor "applicant"}
+                                                           :event/actor "applicant"
+                                                           :workflow.dynamic/handlers ["handler"]}
                                                           {:event/type :application.event/closed
                                                            :event/actor "applicant"}])]
       (is (= #{}
@@ -711,9 +698,8 @@
 
 (deftest test-apply-user-permissions
   (let [application (-> (model/application-view nil {:event/type :application.event/created
-                                                     :event/actor "applicant"})
-                        (assoc-in [:application/workflow :workflow.dynamic/handlers] #{"handler"})
-                        (permissions/give-role-to-users :handler ["handler"])
+                                                     :event/actor "applicant"
+                                                     :workflow.dynamic/handlers #{"handler"}})
                         (permissions/give-role-to-users :role-1 ["user-1"])
                         (permissions/give-role-to-users :role-2 ["user-2"])
                         (permissions/set-role-permissions {:role-1 []
diff --git a/test/clj/rems/db/test_applications.clj b/test/clj/rems/db/test_applications.clj
index ac7ceebc0..c09aab2e5 100644
--- a/test/clj/rems/db/test_applications.clj
+++ b/test/clj/rems/db/test_applications.clj
@@ -87,7 +87,8 @@
               :application/licenses []
               :form/id form-id
               :workflow/id wf-id
-              :workflow/type :workflow/dynamic}
+              :workflow/type :workflow/dynamic
+              :workflow.dynamic/handlers #{}}
              (application-created-event {:application-id 42
                                          :catalogue-item-ids [cat-id]
                                          :time (DateTime. 1000)
@@ -116,7 +117,8 @@
                 :application/licenses []
                 :form/id form-id
                 :workflow/id wf-id
-                :workflow/type :workflow/dynamic}
+                :workflow/type :workflow/dynamic
+                :workflow.dynamic/handlers #{}}
                (application-created-event {:application-id 42
                                            :catalogue-item-ids [cat-id cat-id2]
                                            :time (DateTime. 1000)
@@ -207,7 +209,8 @@
                 :application/licenses [{:license/id lic-id}]
                 :form/id form-id
                 :workflow/id wf-id
-                :workflow/type :workflow/dynamic}
+                :workflow/type :workflow/dynamic
+                :workflow.dynamic/handlers #{}}
                (application-created-event {:application-id 42
                                            :catalogue-item-ids [cat-id2]
                                            :time (DateTime. 1000)
@@ -242,7 +245,37 @@
                 :application/licenses [{:license/id lic-id}]
                 :form/id form-id
                 :workflow/id wf-id2
-                :workflow/type :workflow/dynamic}
+                :workflow/type :workflow/dynamic
+                :workflow.dynamic/handlers #{}}
+               (application-created-event {:application-id 42
+                                           :catalogue-item-ids [cat-id2]
+                                           :time (DateTime. 1000)
+                                           :actor "alice"})))))
+
+    (testing "workflow handlers"
+      (let [wf-id2 (:id (workflow/create-workflow! {:type :dynamic
+                                                    :organization "abc"
+                                                    :title ""
+                                                    :handlers ["handler1" "handler2"]
+                                                    :user-id "owner"}))
+            _ (assert wf-id2)
+            cat-id2 (:id (catalogue/create-catalogue-item! {:title ""
+                                                            :resid res-id
+                                                            :form form-id
+                                                            :wfid wf-id2}))
+            _ (assert cat-id2)]
+        (is (= {:event/type :application.event/created
+                :event/actor "alice"
+                :event/time (DateTime. 1000)
+                :application/id 42
+                :application/external-id nil
+                :application/resources [{:catalogue-item/id cat-id2
+                                         :resource/ext-id "res1"}]
+                :application/licenses []
+                :form/id form-id
+                :workflow/id wf-id2
+                :workflow/type :workflow/dynamic
+                :workflow.dynamic/handlers #{"handler1" "handler2"}}
                (application-created-event {:application-id 42
                                            :catalogue-item-ids [cat-id2]
                                            :time (DateTime. 1000)
diff --git a/test/clj/rems/db/testing.clj b/test/clj/rems/db/testing.clj
index efec65760..7cc5df39e 100644
--- a/test/clj/rems/db/testing.clj
+++ b/test/clj/rems/db/testing.clj
@@ -6,6 +6,7 @@
             [rems.api.applications-v2]
             [rems.config :refer [env]]
             [rems.db.core :as db]
+            [rems.db.dynamic-roles]
             [rems.db.test-data :as test-data]))
 
 (defn test-db-fixture [f]
@@ -21,7 +22,8 @@
 (defn caches-fixture [f]
   ;; no specific teardown. relies on the teardown of test-db-fixture.
   (mount/start #'rems.api.applications-v2/application-cache
-               #'rems.api.applications-v2/all-applications-cache)
+               #'rems.api.applications-v2/all-applications-cache
+               #'rems.db.dynamic-roles/dynamic-roles-cache)
   (f))
 
 (defn test-data-fixture [f]
diff --git a/test/clj/rems/migrations/test_convert_to_dynamic_applications.clj b/test/clj/rems/migrations/test_convert_to_dynamic_applications.clj
index ce88d7fc0..c76dc1380 100644
--- a/test/clj/rems/migrations/test_convert_to_dynamic_applications.clj
+++ b/test/clj/rems/migrations/test_convert_to_dynamic_applications.clj
@@ -133,7 +133,8 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                        {:license/id 2}]
                                 :form/id 1
                                 :workflow/id 7
-                                :workflow/type :workflow/dynamic}
+                                :workflow/type :workflow/dynamic
+                                :workflow.dynamic/handlers #{"developer"}}
                                {:event/type :application.event/draft-saved
                                 :event/actor "developer"
                                 :event/time test-data/creation-time
@@ -174,7 +175,8 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                        {:license/id 2}]
                                 :form/id 1
                                 :workflow/id 7
-                                :workflow/type :workflow/dynamic}
+                                :workflow/type :workflow/dynamic
+                                :workflow.dynamic/handlers #{"developer"}}
                                {:event/type :application.event/draft-saved
                                 :event/actor "developer"
                                 :event/time test-data/creation-time
@@ -220,7 +222,8 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                        {:license/id 2}]
                                 :form/id 1
                                 :workflow/id 7
-                                :workflow/type :workflow/dynamic}
+                                :workflow/type :workflow/dynamic
+                                :workflow.dynamic/handlers #{"developer"}}
                                {:event/type :application.event/draft-saved
                                 :event/actor "developer"
                                 :event/time test-data/creation-time
@@ -272,7 +275,8 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                        {:license/id 2}]
                                 :form/id 1
                                 :workflow/id 7
-                                :workflow/type :workflow/dynamic}
+                                :workflow/type :workflow/dynamic
+                                :workflow.dynamic/handlers #{"developer"}}
                                {:event/type :application.event/draft-saved
                                 :event/actor "developer"
                                 :event/time test-data/creation-time
@@ -324,7 +328,8 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                        {:license/id 2}]
                                 :form/id 1
                                 :workflow/id 7
-                                :workflow/type :workflow/dynamic}
+                                :workflow/type :workflow/dynamic
+                                :workflow.dynamic/handlers #{"developer"}}
                                {:event/type :application.event/draft-saved
                                 :event/actor "developer"
                                 :event/time test-data/creation-time
@@ -379,7 +384,8 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                        {:license/id 3}]
                                 :form/id 1
                                 :workflow/id 7
-                                :workflow/type :workflow/dynamic}
+                                :workflow/type :workflow/dynamic
+                                :workflow.dynamic/handlers #{"developer"}}
                                {:event/type :application.event/draft-saved
                                 :event/actor "alice"
                                 :event/time test-data/creation-time
@@ -436,7 +442,8 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                        {:license/id 2}]
                                 :form/id 1
                                 :workflow/id 7
-                                :workflow/type :workflow/dynamic}
+                                :workflow/type :workflow/dynamic
+                                :workflow.dynamic/handlers #{"developer"}}
                                {:event/type :application.event/draft-saved
                                 :event/actor "alice"
                                 :event/time test-data/creation-time
@@ -495,7 +502,8 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                        {:license/id 2}]
                                 :form/id 1
                                 :workflow/id 7
-                                :workflow/type :workflow/dynamic}
+                                :workflow/type :workflow/dynamic
+                                :workflow.dynamic/handlers #{"developer"}}
                                {:event/type :application.event/draft-saved
                                 :event/actor "alice"
                                 :event/time test-data/creation-time
@@ -541,7 +549,8 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                        {:license/id 2}]
                                 :form/id 1
                                 :workflow/id 7
-                                :workflow/type :workflow/dynamic}
+                                :workflow/type :workflow/dynamic
+                                :workflow.dynamic/handlers #{"developer"}}
                                {:event/type :application.event/draft-saved
                                 :event/actor "alice"
                                 :event/time (-> application :dynamic-events (nth 1) :event/time)
@@ -595,7 +604,8 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                        {:license/id 2}]
                                 :form/id 1
                                 :workflow/id 7
-                                :workflow/type :workflow/dynamic}
+                                :workflow/type :workflow/dynamic
+                                :workflow.dynamic/handlers #{"developer"}}
                                {:event/type :application.event/draft-saved
                                 :event/actor "alice"
                                 :event/time (-> application :dynamic-events (nth 1) :event/time)
@@ -656,7 +666,8 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                        {:license/id 2}]
                                 :form/id 1
                                 :workflow/id 7
-                                :workflow/type :workflow/dynamic}
+                                :workflow/type :workflow/dynamic
+                                :workflow.dynamic/handlers #{"developer"}}
                                {:event/type :application.event/draft-saved
                                 :event/actor "alice"
                                 :event/time (-> application :dynamic-events (nth 1) :event/time)
@@ -708,7 +719,8 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                        {:license/id 2}]
                                 :form/id 1
                                 :workflow/id 7
-                                :workflow/type :workflow/dynamic}
+                                :workflow/type :workflow/dynamic
+                                :workflow.dynamic/handlers #{"developer"}}
                                {:event/type :application.event/draft-saved
                                 :event/actor "alice"
                                 :event/time (-> application :dynamic-events (nth 1) :event/time)
diff --git a/test/clj/rems/poller/test_email.clj b/test/clj/rems/poller/test_email.clj
index 5e0e6ea92..bc7782361 100644
--- a/test/clj/rems/poller/test_email.clj
+++ b/test/clj/rems/poller/test_email.clj
@@ -1,9 +1,8 @@
 (ns rems.poller.test-email
   (:require [clojure.test :refer :all]
             [mount.core :as mount]
-            [rems.application.model :as model]
-            [rems.config]
-            [rems.locales]
+            rems.config
+            rems.locales
             [rems.poller.email :refer :all]
             [rems.text :as text]
             [rems.workflow.dynamic :as dynamic]))
@@ -11,14 +10,12 @@
 (use-fixtures
   :once
   (fn [f]
-    (mount/start #'rems.config/env
-                 #'rems.locales/translations)
+    (mount/start #'rems.config/env #'rems.locales/translations)
     (f)
     (mount/stop)))
 
 (defn events-to-emails [events]
-  (let [application (-> (dynamic/apply-events nil events)
-                        (model/enrich-workflow-handlers {5 {:workflow {:handlers ["handler" "assistant"]}}}))]
+  (let [application (dynamic/apply-events nil events)]
     (text/with-language :en
       (fn [] (mapv #(#'rems.poller.email/event-to-emails-impl % application) events)))))
 
@@ -27,8 +24,8 @@
                       :application/external-id "2001/3"
                       :event/type :application.event/created
                       :event/actor "applicant"
-                      :workflow/id 5
-                      :workflow/type :workflow/dynamic}
+                      :workflow/type :workflow/dynamic
+                      :workflow.dynamic/handlers #{"handler" "assistant"}}
                      {:application/id 7
                       :event/type :application.event/submitted
                       :event/actor "applicant"}]]
@@ -121,9 +118,9 @@
                 :body "Dear member,\nYour application 2001/3 has been closed.\nView your application: http://localhost:3001/#/application/7"}]]
              (events-to-emails events))))
     (let [events (conj base-events
-                       {:application/id 7
-                        :event/type :application.event/rejected
-                        :event/actor "handler"})]
+                      {:application/id 7
+                       :event/type :application.event/rejected
+                       :event/actor "handler"})]
       (is (= [[]
               []
               [{:subject "Your application has been rejected",
diff --git a/test/clj/rems/test_locales.clj b/test/clj/rems/test_locales.clj
index 8e4c59b8e..5db7c8c5c 100644
--- a/test/clj/rems/test_locales.clj
+++ b/test/clj/rems/test_locales.clj
@@ -99,20 +99,7 @@
     (let [translations (locales/load-translations {:languages [:en]
                                                    :translations-directory "translations/"
                                                    :theme-path "example-theme/theme.edn"})]
-      (is (= "Active" (getx-in translations [:en :t :administration :active])))))
-  ;; TODO: This should rather be part of validation of all custom themes, but
-  ;;   it's probably better to at least have it here than not at all.
-  (testing "extra translations don't add keys that are not defined in original"
-    (doseq [lang [:en :fi]]
-      (let [translations
-            (locales/load-translations {:languages [lang]
-                                        :translations-directory "translations/"
-                                        :theme-path "example-theme/theme.edn"})
-            translations-without-extras
-            (locales/load-translations {:languages [lang]
-                                        :translations-directory "translations/"})]
-        (is (= (map-structure translations)
-               (map-structure translations-without-extras)))))))
+      (is (= "Active" (getx-in translations [:en :t :administration :active]))))))
 
 (deftest theme-path-given-no-extra-translations
   (testing "translations work with theme-path in config and no extra-translations"
diff --git a/test/clj/rems/test_performance.clj b/test/clj/rems/test_performance.clj
index c8b32124c..db427f310 100644
--- a/test/clj/rems/test_performance.clj
+++ b/test/clj/rems/test_performance.clj
@@ -5,7 +5,8 @@
             [mount.core :as mount]
             [rems.api.applications-v2 :as applications-v2]
             [rems.api.reviews :as reviews]
-            [rems.db.applications :as applications])
+            [rems.db.applications :as applications]
+            [rems.db.dynamic-roles :as dynamic-roles])
   (:import [java.util Locale]))
 
 (defn run-benchmark [benchmark]
@@ -48,7 +49,6 @@
 (defn benchmark-get-all-applications []
   (let [test-get-all-unrestricted-applications #(doall (applications-v2/get-all-unrestricted-applications))
         test-get-all-applications #(doall (applications-v2/get-all-applications "alice"))
-        test-get-all-application-roles #(doall (applications-v2/get-all-application-roles "developer"))
         test-get-own-applications #(doall (applications-v2/get-own-applications "alice"))
         ;; developer can view much more applications than alice, so it takes longer to filter reviews from all apps
         test-get-open-reviews #(doall (reviews/get-open-reviews "developer"))
@@ -59,23 +59,20 @@
                  (mount/start #'applications-v2/all-applications-cache)
                  (test-get-all-unrestricted-applications))]
     (run-benchmarks [{:name "get-all-unrestricted-applications, no cache"
-                      :benchmark test-get-all-unrestricted-applications
-                      :setup no-cache}
+                      :setup no-cache
+                      :benchmark test-get-all-unrestricted-applications}
                      {:name "get-all-unrestricted-applications, cached"
-                      :benchmark test-get-all-unrestricted-applications
-                      :setup cached}
+                      :setup cached
+                      :benchmark test-get-all-unrestricted-applications}
                      {:name "get-all-applications, cached"
-                      :benchmark test-get-all-applications
-                      :setup cached}
-                     {:name "get-all-application-roles, cached"
-                      :benchmark test-get-all-application-roles
-                      :setup cached}
+                      :setup cached
+                      :benchmark test-get-all-applications}
                      {:name "get-own-applications, cached"
-                      :benchmark test-get-own-applications
-                      :setup cached}
+                      :setup cached
+                      :benchmark test-get-own-applications}
                      {:name "get-open-reviews, cached"
-                      :benchmark test-get-open-reviews
-                      :setup cached}])
+                      :setup cached
+                      :benchmark test-get-open-reviews}])
     (println "cache size" (mm/measure applications-v2/all-applications-cache))))
 
 (defn benchmark-get-application []
@@ -87,13 +84,29 @@
                  (mount/start #'applications-v2/application-cache)
                  (test-get-application))]
     (run-benchmarks [{:name "get-application, no cache"
-                      :benchmark test-get-application
-                      :setup no-cache}
+                      :setup no-cache
+                      :benchmark test-get-application}
                      {:name "get-application, cached"
-                      :benchmark test-get-application
-                      :setup cached}])
+                      :setup cached
+                      :benchmark test-get-application}])
     (println "cache size" (mm/measure applications-v2/application-cache))))
 
+(defn benchmark-get-dynamic-roles []
+  (let [test-get-roles #(doall (dynamic-roles/get-roles "developer"))
+        no-cache (fn []
+                   (mount/stop #'dynamic-roles/dynamic-roles-cache))
+        cached (fn []
+                 (mount/stop #'dynamic-roles/dynamic-roles-cache)
+                 (mount/start #'dynamic-roles/dynamic-roles-cache)
+                 (test-get-roles))]
+    (run-benchmarks [{:name "get-roles, no cache"
+                      :setup no-cache
+                      :benchmark test-get-roles}
+                     {:name "get-roles, cached"
+                      :setup cached
+                      :benchmark test-get-roles}])
+    (println "cache size" (mm/measure dynamic-roles/dynamic-roles-cache))))
+
 (comment
   ;; Note: If clj-memory-meter throws InaccessibleObjectException on Java 9+,
   ;;       you *could* work around it by adding `--add-opens` JVM options, but
@@ -101,4 +114,5 @@
   ;;       easily be avoided.
   (benchmark-get-events)
   (benchmark-get-all-applications)
-  (benchmark-get-application))
+  (benchmark-get-application)
+  (benchmark-get-dynamic-roles))
diff --git a/test/clj/rems/workflow/test_dynamic.clj b/test/clj/rems/workflow/test_dynamic.clj
index 6245e1b8b..aa1eb5696 100644
--- a/test/clj/rems/workflow/test_dynamic.clj
+++ b/test/clj/rems/workflow/test_dynamic.clj
@@ -17,18 +17,17 @@
                                     :event/time test-time
                                     :event/actor applicant-user-id
                                     :application/id 123
-                                    :application/external-id nil
                                     :application/resources []
                                     :application/licenses []
                                     :form/id 1
                                     :workflow/id 1
-                                    :workflow/type :workflow/dynamic})
-(def ^:private dummy-workflows {1 {:workflow {:handlers [handler-user-id]}}})
+                                    :workflow/type :workflow/dynamic
+                                    :application/external-id nil
+                                    :workflow.dynamic/handlers #{handler-user-id}})
 
 (defn apply-events [application events]
   (events/validate-events events)
-  (-> (dynamic/apply-events application events)
-      (model/enrich-workflow-handlers dummy-workflows)))
+  (dynamic/apply-events application events))
 
 (defn- fail-command
   ([application cmd]
@@ -170,7 +169,6 @@
                        :event/time test-time
                        :event/actor applicant-user-id
                        :application/id 123
-                       :application/external-id nil
                        :application/resources [{:catalogue-item/id 10
                                                 :resource/ext-id "urn:11"}
                                                {:catalogue-item/id 20
@@ -179,7 +177,9 @@
                                               {:license/id 31}]
                        :form/id 40
                        :workflow/id 50
-                       :workflow/type :workflow/dynamic}
+                       :workflow/type :workflow/dynamic
+                       :application/external-id nil
+                       :workflow.dynamic/handlers #{handler-user-id}}
         draft-saved-event {:event/type :application.event/draft-saved
                            :event/time test-time
                            :event/actor applicant-user-id