Repository: CSCfi/rems
Pull request #1138 (2019-04-16 11:43:18)

Refactor stuff


Patch:
diff --git a/src/clj/rems/api/applications_v2.clj b/src/clj/rems/api/applications_v2.clj
index fa631009e..6e3655b63 100644
--- a/src/clj/rems/api/applications_v2.clj
+++ b/src/clj/rems/api/applications_v2.clj
@@ -12,8 +12,9 @@
             [rems.db.licenses :as licenses]
             [rems.db.users :as users]
             [rems.permissions :as permissions]
+            [rems.scheduler :as scheduler]
             [rems.util :refer [getx atom?]])
-  (:import [java.util.concurrent TimeUnit ScheduledThreadPoolExecutor ExecutorService]))
+  (:import [org.joda.time Duration]))
 
 (defn- get-form [form-id]
   (-> (form/get-form form-id)
@@ -46,7 +47,7 @@
   "Returns the full application state without any user permission
    checks and filtering of sensitive information. Don't expose via APIs."
   [application-id]
-  (let [events (applications/get-dynamic-application-events application-id)
+  (let [events (applications/get-application-events application-id)
         cache-key [application-id (count events)]
         build-app (fn [_] (model/build-application-view events injections))]
     (if (empty? events)
@@ -149,10 +150,5 @@
 
 ;; empty the cache occasionally in case some of the injected entities are changed
 (mount/defstate all-applications-cache-reloader
-  ;; TODO: consider refactoring this mount-cron-thing which is duplicated in a few places
-  :start (doto (ScheduledThreadPoolExecutor. 1)
-           (.scheduleWithFixedDelay reload-cache! 1 1 TimeUnit/HOURS))
-  :stop (let [executor (doto ^ExecutorService all-applications-cache-reloader
-                         (.shutdownNow))]
-          (when-not (.awaitTermination executor 1 TimeUnit/MINUTES)
-            (throw (IllegalStateException. "did not terminate")))))
+  :start (scheduler/start! reload-cache! (Duration/standardHours 1))
+  :stop (scheduler/stop! all-applications-cache-reloader))
diff --git a/src/clj/rems/application/commands.clj b/src/clj/rems/application/commands.clj
index 6a983b9db..8385cf8f8 100644
--- a/src/clj/rems/application/commands.clj
+++ b/src/clj/rems/application/commands.clj
@@ -1,9 +1,6 @@
 (ns rems.application.commands
-  (:require [clojure.test :refer :all]
-            [rems.util :refer [getx]]
-            [schema.core :as s]
-            [schema-refined.core :as r]
-            [rems.util :refer [assert-ex try-catch-ex]])
+  (:require [rems.util :refer [getx]]
+            [schema.core :as s])
   (:import (org.joda.time DateTime)))
 
 ;; can't use defschema for this alias since s/Str is just String, which doesn't have metadata
@@ -115,31 +112,8 @@
    :application.command/uninvite-member UninviteMemberCommand
    #_:application.command/withdraw})
 
-(s/defschema Command
-  (merge (apply r/StructDispatch :type (flatten (seq command-schemas)))
-         CommandInternal))
-
 (defn validate-command [cmd]
-  (assert-ex (contains? command-schemas (:type cmd)) {:error {:type ::unknown-type}
-                                                      :value cmd})
-  (s/validate Command cmd))
-
-(deftest test-validate-command
-  (testing "check specific command schema"
-    (is (validate-command {:application-id 42
-                           :type :application.command/submit
-                           :time (DateTime.)
-                           :actor "applicant"})))
-  (testing "missing event specific key"
-    (is (= {:actor 'missing-required-key}
-           (:error
-            (try-catch-ex
-             (validate-command {:application-id 42
-                                :type :application.command/submit
-                                :time (DateTime.)}))))))
-  (testing "unknown event type"
-    (is (= {:type ::unknown-type}
-           (:error
-            (try-catch-ex
-             (validate-command
-              {:type :does-not-exist})))))))
+  (let [type (:type cmd)
+        schema (merge CommandInternal
+                      (getx command-schemas type))]
+    (s/validate schema cmd)))
diff --git a/src/clj/rems/application/events.clj b/src/clj/rems/application/events.clj
index aa8150777..7b66f33b6 100644
--- a/src/clj/rems/application/events.clj
+++ b/src/clj/rems/application/events.clj
@@ -1,8 +1,7 @@
 (ns rems.application.events
   (:require [clojure.test :refer :all]
             [schema-refined.core :as r]
-            [schema.core :as s]
-            [rems.util :refer [assert-ex try-catch-ex]])
+            [schema.core :as s])
   (:import (org.joda.time DateTime)))
 
 ;; can't use defschema for this alias since s/Str is just String, which doesn't have metadata
@@ -135,8 +134,6 @@
   (apply r/dispatch-on :event/type (flatten (seq event-schemas))))
 
 (defn validate-event [event]
-  (assert-ex (contains? event-schemas (:event/type event)) {:error {:event/type ::unknown-type}
-                                                            :value event})
   (s/validate Event event))
 
 (defn validate-events [events]
@@ -170,33 +167,10 @@
                      :event/actor "foo"
                      :application/id 123}))))
   (testing "unknown event type"
+    ;; TODO: improve error message to show the actual and expected event types
     (is (= "(not (some-matching-condition? a-clojure.lang.PersistentArrayMap))"
            (pr-str (s/check Event
                             {:event/type :foo
                              :event/time (DateTime.)
                              :event/actor "foo"
                              :application/id 123}))))))
-
-(deftest test-validate-event
-  (testing "check specific event schema"
-    (is (validate-event {:event/type :application.event/submitted
-                         :event/time (DateTime.)
-                         :event/actor "foo"
-                         :application/id 123})))
-  (testing "missing event specific key"
-    (is (= {:application/comment 'missing-required-key}
-           (:error
-            (try-catch-ex
-             (validate-event {:event/type :application.event/approved
-                              :event/time (DateTime.)
-                              :event/actor "foo"
-                              :application/id 123}))))))
-  (testing "unknown event type"
-    (is (= {:event/type ::unknown-type}
-           (:error
-            (try-catch-ex
-             (validate-event
-              {:event/type :does-not-exist
-               :event/time (DateTime.)
-               :event/actor "foo"
-               :application/id 123})))))))
diff --git a/src/clj/rems/application/events_cache.clj b/src/clj/rems/application/events_cache.clj
index e12ca4acf..f0b9952d3 100644
--- a/src/clj/rems/application/events_cache.clj
+++ b/src/clj/rems/application/events_cache.clj
@@ -17,7 +17,7 @@
   [cache update-fn]
   (let [cache-enabled? (atom? cache)
         cached (if cache-enabled? @cache empty-cache)
-        new-events (applications/get-dynamic-application-events-since (:last-processed-event-id cached))]
+        new-events (applications/get-all-events-since (:last-processed-event-id cached))]
     (if (empty? new-events)
       (:state cached)
       (let [updated {:state (update-fn (:state cached) new-events)
diff --git a/src/clj/rems/db/applications.clj b/src/clj/rems/db/applications.clj
index 6e42224e7..fa9ef7515 100644
--- a/src/clj/rems/db/applications.clj
+++ b/src/clj/rems/db/applications.clj
@@ -634,18 +634,18 @@
   (assoc (-> event :eventdata json->event)
          :event/id (:id event)))
 
-;; TODO: remove "dynamic" from names
-(defn get-dynamic-application-events [application-id]
+(defn get-application-events [application-id]
   (map fix-event-from-db (db/get-application-events {:application application-id})))
 
-(defn get-dynamic-application-events-since [event-id]
+(defn get-all-events-since [event-id]
   (map fix-event-from-db (db/get-application-events-since {:id event-id})))
 
+;; TODO: remove "dynamic" from names
 (defn get-dynamic-application-state [application-id]
   (let [application (or (first (db/get-applications {:id application-id}))
                         (throw (rems.InvalidRequestException.
                                 (str "Application " application-id " not found"))))
-        events (get-dynamic-application-events application-id)
+        events (get-application-events application-id)
         application (assoc application
                            :state :application.state/draft
                            :dynamic-events events
@@ -662,7 +662,7 @@
       (throw-forbidden))
     application))
 
-(defn add-dynamic-event! [event]
+(defn add-event! [event]
   (db/add-application-event! {:application (:application/id event)
                               :user (:event/actor event)
                               :comment nil
@@ -719,7 +719,7 @@
        :workflow.dynamic/handlers (set (:handlers workflow))})))
 
 (defn add-application-created-event! [opts]
-  (add-dynamic-event! (application-created-event (assoc opts :allocate-external-id? true))))
+  (add-event! (application-created-event (assoc opts :allocate-external-id? true))))
 
 (defn- get-workflow-id-for-catalogue-items [catalogue-item-ids]
   (:workflow/id (application-created-event {:catalogue-item-ids catalogue-item-ids})))
@@ -763,7 +763,7 @@
   (let [app (get-dynamic-application-state (:application-id cmd))
         result (dynamic/handle-command cmd app db-injections)]
     (if (:success result)
-      (add-dynamic-event! (:result result))
+      (add-event! (:result result))
       result)))
 
 (defn is-dynamic-handler? [user-id application]
diff --git a/src/clj/rems/migrations/convert_to_dynamic_applications.clj b/src/clj/rems/migrations/convert_to_dynamic_applications.clj
index 192bbe610..7c3948763 100644
--- a/src/clj/rems/migrations/convert_to_dynamic_applications.clj
+++ b/src/clj/rems/migrations/convert_to_dynamic_applications.clj
@@ -32,83 +32,83 @@
                                                                            (map :id))
                                                   :time (:start application)
                                                   :actor (:applicantuserid application)})
-    (applications/add-dynamic-event! {:event/type :application.event/draft-saved
-                                      :event/time (:start application)
-                                      :event/actor (:applicantuserid application)
-                                      :application/id (:id application)
-                                      :application/field-values (->> (:items form)
-                                                                     (map (fn [item]
-                                                                            [(:id item) (:value item)]))
-                                                                     (into {}))})
-    (applications/add-dynamic-event! {:event/type :application.event/licenses-accepted
-                                      :event/time (:start application)
-                                      :event/actor (:applicantuserid application)
-                                      :application/id (:id application)
-                                      :application/accepted-licenses (->> (:licenses form)
-                                                                          (filter :approved)
-                                                                          (map :id)
-                                                                          set)})
+    (applications/add-event! {:event/type :application.event/draft-saved
+                              :event/time (:start application)
+                              :event/actor (:applicantuserid application)
+                              :application/id (:id application)
+                              :application/field-values (->> (:items form)
+                                                             (map (fn [item]
+                                                                    [(:id item) (:value item)]))
+                                                             (into {}))})
+    (applications/add-event! {:event/type :application.event/licenses-accepted
+                              :event/time (:start application)
+                              :event/actor (:applicantuserid application)
+                              :application/id (:id application)
+                              :application/accepted-licenses (->> (:licenses form)
+                                                                  (filter :approved)
+                                                                  (map :id)
+                                                                  set)})
     (doseq [event (:events application)]
       (case (:event event)
         "save" nil ; skip - the draft-saved event is produced separately and the legacy applications don't have form history
-        "apply" (applications/add-dynamic-event! {:event/type :application.event/submitted
-                                                  :event/time (:time event)
-                                                  :event/actor (:userid event)
-                                                  :application/id (:id application)})
-        "reject" (applications/add-dynamic-event! {:event/type :application.event/rejected
+        "apply" (applications/add-event! {:event/type :application.event/submitted
+                                          :event/time (:time event)
+                                          :event/actor (:userid event)
+                                          :application/id (:id application)})
+        "reject" (applications/add-event! {:event/type :application.event/rejected
+                                           :event/time (:time event)
+                                           :event/actor (:userid event)
+                                           :application/id (:id application)
+                                           :application/comment (or (:comment event) "")})
+        "approve" (applications/add-event! {:event/type :application.event/approved
+                                            :event/time (:time event)
+                                            :event/actor (:userid event)
+                                            :application/id (:id application)
+                                            :application/comment (or (:comment event) "")})
+        "autoapprove" (applications/add-event! {:event/type :application.event/approved
+                                                :event/time (:time event)
+                                                :event/actor (:userid event)
+                                                :application/id (:id application)
+                                                :application/comment ""})
+        "return" (applications/add-event! {:event/type :application.event/returned
+                                           :event/time (:time event)
+                                           :event/actor (:userid event)
+                                           :application/id (:id application)
+                                           :application/comment (or (:comment event) "")})
+        "review" (applications/add-event! {:event/type :application.event/commented
+                                           :event/time (:time event)
+                                           :event/actor (:userid event)
+                                           :application/id (:id application)
+                                           ;; TODO: request-id doesn't make much sense for these old applications - make it optional?
+                                           :application/request-id (UUID. 0 0)
+                                           :application/comment (or (:comment event) "")})
+        "review-request" (applications/add-event! {:event/type :application.event/comment-requested
                                                    :event/time (:time event)
+                                                   ;; TODO: the review request's actor is not known; can we guess the approver?
                                                    :event/actor (:userid event)
                                                    :application/id (:id application)
+                                                   :application/request-id (let [request-id (UUID/randomUUID)]
+                                                                             (swap! comment-requests-by-commenter
+                                                                                    assoc (:userid event) request-id)
+                                                                             request-id)
+                                                   :application/commenters [(:userid event)]
                                                    :application/comment (or (:comment event) "")})
-        "approve" (applications/add-dynamic-event! {:event/type :application.event/approved
-                                                    :event/time (:time event)
-                                                    :event/actor (:userid event)
-                                                    :application/id (:id application)
-                                                    :application/comment (or (:comment event) "")})
-        "autoapprove" (applications/add-dynamic-event! {:event/type :application.event/approved
-                                                        :event/time (:time event)
-                                                        :event/actor (:userid event)
-                                                        :application/id (:id application)
-                                                        :application/comment ""})
-        "return" (applications/add-dynamic-event! {:event/type :application.event/returned
-                                                   :event/time (:time event)
-                                                   :event/actor (:userid event)
-                                                   :application/id (:id application)
-                                                   :application/comment (or (:comment event) "")})
-        "review" (applications/add-dynamic-event! {:event/type :application.event/commented
-                                                   :event/time (:time event)
-                                                   :event/actor (:userid event)
-                                                   :application/id (:id application)
-                                                   ;; TODO: request-id doesn't make much sense for these old applications - make it optional?
-                                                   :application/request-id (UUID. 0 0)
-                                                   :application/comment (or (:comment event) "")})
-        "review-request" (applications/add-dynamic-event! {:event/type :application.event/comment-requested
-                                                           :event/time (:time event)
-                                                           ;; TODO: the review request's actor is not known; can we guess the approver?
-                                                           :event/actor (:userid event)
-                                                           :application/id (:id application)
-                                                           :application/request-id (let [request-id (UUID/randomUUID)]
-                                                                                     (swap! comment-requests-by-commenter
-                                                                                            assoc (:userid event) request-id)
-                                                                                     request-id)
-                                                           :application/commenters [(:userid event)]
-                                                           :application/comment (or (:comment event) "")})
-        "third-party-review" (applications/add-dynamic-event! {:event/type :application.event/commented
-                                                               :event/time (:time event)
-                                                               :event/actor (:userid event)
-                                                               :application/id (:id application)
-                                                               :application/request-id (get @comment-requests-by-commenter (:userid event))
-                                                               :application/comment (or (:comment event) "")})
-        "withdraw" (applications/add-dynamic-event! {:event/type :application.event/returned
-                                                     :event/time (:time event)
-                                                     :event/actor (:userid event)
-                                                     :application/id (:id application)
-                                                     :application/comment (or (:comment event) "")})
-        "close" (applications/add-dynamic-event! {:event/type :application.event/closed
-                                                  :event/time (:time event)
-                                                  :event/actor (:userid event)
-                                                  :application/id (:id application)
-                                                  :application/comment (or (:comment event) "")})))))
+        "third-party-review" (applications/add-event! {:event/type :application.event/commented
+                                                       :event/time (:time event)
+                                                       :event/actor (:userid event)
+                                                       :application/id (:id application)
+                                                       :application/request-id (get @comment-requests-by-commenter (:userid event))
+                                                       :application/comment (or (:comment event) "")})
+        "withdraw" (applications/add-event! {:event/type :application.event/returned
+                                             :event/time (:time event)
+                                             :event/actor (:userid event)
+                                             :application/id (:id application)
+                                             :application/comment (or (:comment event) "")})
+        "close" (applications/add-event! {:event/type :application.event/closed
+                                          :event/time (:time event)
+                                          :event/actor (:userid event)
+                                          :application/id (:id application)
+                                          :application/comment (or (:comment event) "")})))))
 
 (defn migrate-all-applications! [new-workflow-id]
   (conman/with-transaction [*db* {:isolation :serializable}]
diff --git a/src/clj/rems/poller/common.clj b/src/clj/rems/poller/common.clj
index ccfc46993..bd3e4979f 100644
--- a/src/clj/rems/poller/common.clj
+++ b/src/clj/rems/poller/common.clj
@@ -17,7 +17,7 @@
 (defn run-event-poller [name-kw process-event!]
   ;; This isn't thread-safe but ScheduledThreadPoolExecutor guarantees exclusion
   (let [prev-state (get-poller-state name-kw)
-        events (applications/get-dynamic-application-events-since (:last-processed-event-id prev-state))]
+        events (applications/get-all-events-since (:last-processed-event-id prev-state))]
     (log/debug name-kw "running with state" (pr-str prev-state))
     (try
       (doseq [e events]
@@ -45,7 +45,7 @@
                          (swap! processed conj event))
         poller-state (atom {:last-processed-event-id 0})
         run #(run-event-poller :test process-event!)]
-    (with-redefs [applications/get-dynamic-application-events-since (fn [id] (filterv #(< id (:event/id %)) @events))
+    (with-redefs [applications/get-all-events-since (fn [id] (filterv #(< id (:event/id %)) @events))
                   get-poller-state (fn [_] @poller-state)
                   set-poller-state! (fn [_ state] (reset! poller-state state))]
       (testing "no events, nothing should happen"
diff --git a/src/clj/rems/poller/email.clj b/src/clj/rems/poller/email.clj
index 7d221b799..2c482dbf0 100644
--- a/src/clj/rems/poller/email.clj
+++ b/src/clj/rems/poller/email.clj
@@ -9,9 +9,10 @@
             [rems.db.applications :as applications]
             [rems.db.users :as users]
             [rems.poller.common :as common]
+            [rems.scheduler :as scheduler]
             [rems.text :refer [text text-format with-language]]
             [rems.util :as util])
-  (:import [java.util.concurrent ScheduledThreadPoolExecutor TimeUnit ExecutorService]))
+  (:import [org.joda.time Duration]))
 
 ;;; Mapping events to emails
 
@@ -151,7 +152,7 @@
 ;; 4. open http://localhost:8025 in your browser to view the emails
 
 (defn mark-all-emails-as-sent! []
-  (let [events (applications/get-dynamic-application-events-since 0)
+  (let [events (applications/get-all-events-since 0)
         last-id (:event/id (last events))]
     (common/set-poller-state! ::poller {:last-processed-event-id last-id})))
 
@@ -179,12 +180,8 @@
                                            (send-email! mail))))))
 
 (mount/defstate email-poller
-  :start (doto (ScheduledThreadPoolExecutor. 1)
-           (.scheduleWithFixedDelay run 10 10 TimeUnit/SECONDS))
-  :stop (let [executor (doto ^ExecutorService email-poller
-                         (.shutdownNow))]
-          (when-not (.awaitTermination executor 1 TimeUnit/MINUTES)
-            (throw (IllegalStateException. "did not terminate")))))
+  :start (scheduler/start! run (Duration/standardSeconds 10))
+  :stop (scheduler/stop! email-poller))
 
 (comment
   (mount/start #{#'email-poller})
diff --git a/src/clj/rems/poller/entitlements.clj b/src/clj/rems/poller/entitlements.clj
index 0c49adefd..1beb9446b 100644
--- a/src/clj/rems/poller/entitlements.clj
+++ b/src/clj/rems/poller/entitlements.clj
@@ -5,8 +5,9 @@
             [mount.core :as mount]
             [rems.api.applications-v2 :as applications-v2]
             [rems.db.entitlements :as entitlements]
-            [rems.poller.common :as common])
-  (:import [java.util.concurrent ScheduledThreadPoolExecutor TimeUnit ExecutorService]))
+            [rems.poller.common :as common]
+            [rems.scheduler :as scheduler])
+  (:import [org.joda.time Duration]))
 
 (defn- entitlements-for-event [event]
   ;; we filter by event here, and by state in update-entitlements-for.
@@ -23,9 +24,5 @@
   (common/run-event-poller ::poller entitlements-for-event))
 
 (mount/defstate entitlements-poller
-  :start (doto (ScheduledThreadPoolExecutor. 1)
-           (.scheduleWithFixedDelay run 10 10 TimeUnit/SECONDS))
-  :stop (let [executor (doto ^ExecutorService entitlements-poller
-                         (.shutdownNow))]
-          (when-not (.awaitTermination executor 1 TimeUnit/MINUTES)
-            (throw (IllegalStateException. "did not terminate")))))
+  :start (scheduler/start! run (Duration/standardSeconds 10))
+  :stop (scheduler/stop! entitlements-poller))
diff --git a/src/clj/rems/scheduler.clj b/src/clj/rems/scheduler.clj
new file mode 100644
index 000000000..864740859
--- /dev/null
+++ b/src/clj/rems/scheduler.clj
@@ -0,0 +1,13 @@
+(ns rems.scheduler
+  (:import [java.util.concurrent ScheduledThreadPoolExecutor TimeUnit ExecutorService]
+           [org.joda.time Duration]))
+
+(defn ^ExecutorService start! [f ^Duration interval]
+  (let [interval-millis (.getMillis interval)]
+    (doto (ScheduledThreadPoolExecutor. 1)
+      (.scheduleWithFixedDelay f interval-millis interval-millis TimeUnit/MILLISECONDS))))
+
+(defn stop! [^ExecutorService scheduler]
+  (.shutdownNow scheduler)
+  (when-not (.awaitTermination scheduler 5 TimeUnit/MINUTES)
+    (throw (IllegalStateException. "did not terminate"))))
diff --git a/src/clj/rems/util.clj b/src/clj/rems/util.clj
index 15d59d4ca..9bbb1fa0f 100644
--- a/src/clj/rems/util.clj
+++ b/src/clj/rems/util.clj
@@ -82,18 +82,3 @@
 
 (defn atom? [x]
   (instance? Atom x))
-
-(defmacro assert-ex
-  "Like assert but throw the result with ex-info and not as string. "
-  ([x message]
-   `(when-not ~x
-      (throw (ex-info (str "Assert failed: " ~message "\n" (pr-str '~x))
-                      (merge ~message {:expression '~x}))))))
-
-(defmacro try-catch-ex
-  "Wraps the code in `try` and `catch` and automatically unwraps the possible exception `ex-data` into regular result."
-  [& body]
-  `(try
-     ~@body
-     (catch clojure.lang.ExceptionInfo e#
-       (ex-data e#))))


Test patch:
diff --git a/test/clj/rems/api/test_applications.clj b/test/clj/rems/api/test_applications.clj
index 8e703d8ce..a45692fec 100644
--- a/test/clj/rems/api/test_applications.clj
+++ b/test/clj/rems/api/test_applications.clj
@@ -208,8 +208,7 @@
       (is (= {:success false
               :errors [{:type "forbidden"}]}
              (send-command "" {:type :application.command/approve
-                               :application-id application-id
-                               :comment ""}))
+                               :comment "" :application-id application-id}))
           "user should be forbidden to send command"))
 
     (testing "send command with a user that is not a handler"
diff --git a/test/clj/rems/application/test_events_cache.clj b/test/clj/rems/application/test_events_cache.clj
index 33127a1f7..f13f1dfcb 100644
--- a/test/clj/rems/application/test_events_cache.clj
+++ b/test/clj/rems/application/test_events_cache.clj
@@ -36,7 +36,7 @@
                :event/time (time/now)
                :event/actor user-id
                :application/id app-id}]
-    (applications/add-dynamic-event! event)
+    (applications/add-event! event)
     event))
 
 ;; in case the cache uses mount and somebody forgets to start it
diff --git a/test/clj/rems/db/test_applications.clj b/test/clj/rems/db/test_applications.clj
index c09aab2e5..529ee6c24 100644
--- a/test/clj/rems/db/test_applications.clj
+++ b/test/clj/rems/db/test_applications.clj
@@ -15,7 +15,6 @@
             [rems.db.testing :refer [test-db-fixture rollback-db-fixture test-data-fixture]]
             [rems.db.workflow :as workflow]
             [rems.test-db :as test-db]
-            [rems.util :refer [try-catch-ex]]
             [schema-generators.generators :as sg])
   (:import (org.joda.time DateTime DateTimeZone)
            (clojure.lang ExceptionInfo)))
@@ -39,9 +38,7 @@
         (is (= event (-> event event->json json->event))))))
 
   (testing "event->json validates events"
-    (is (not
-         (:rems.event/validate-event
-          (try-catch-ex (event->json {}))))))
+    (is (thrown-with-msg? ExceptionInfo #"Value does not match schema" (event->json {}))))
 
   (testing "json->event validates events"
     (is (thrown-with-msg? ExceptionInfo #"Value does not match schema" (json->event "{}"))))
diff --git a/test/clj/rems/test_performance.clj b/test/clj/rems/test_performance.clj
index 1b9b36294..db427f310 100644
--- a/test/clj/rems/test_performance.clj
+++ b/test/clj/rems/test_performance.clj
@@ -5,12 +5,14 @@
             [mount.core :as mount]
             [rems.api.applications-v2 :as applications-v2]
             [rems.api.reviews :as reviews]
+            [rems.db.applications :as applications]
             [rems.db.dynamic-roles :as dynamic-roles])
   (:import [java.util Locale]))
 
 (defn run-benchmark [benchmark]
   (println "\n=====" (:name benchmark) "=====")
-  ((:setup benchmark))
+  (when-let [setup (:setup benchmark)]
+    (setup))
   (let [result (criterium/with-progress-reporting
                  (criterium/quick-benchmark ((:benchmark benchmark)) {}))]
     (criterium/report-result result)
@@ -32,6 +34,18 @@
                (->> (select-keys result [:low :mean :high])
                     (map-vals #(String/format Locale/ENGLISH "%.3f ms" (to-array [(* 1000 %)]))))))))
 
+(defn benchmark-get-events []
+  (let [last-event-id (:event/id (last (applications/get-all-events-since 0)))
+        test-get-all-events-since-beginning #(doall (applications/get-all-events-since 0))
+        test-get-all-events-since-end #(doall (applications/get-all-events-since last-event-id))
+        test-get-application-events #(doall (applications/get-application-events 12))]
+    (run-benchmarks [{:name "get-all-events-since, all events"
+                      :benchmark test-get-all-events-since-beginning}
+                     {:name "get-all-events-since, zero new events"
+                      :benchmark test-get-all-events-since-end}
+                     {:name "get-application-events"
+                      :benchmark test-get-application-events}])))
+
 (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"))
@@ -98,6 +112,7 @@
   ;;       you *could* work around it by adding `--add-opens` JVM options, but
   ;;       the root cause is probably that there is a lazy sequence that could
   ;;       easily be avoided.
+  (benchmark-get-events)
   (benchmark-get-all-applications)
   (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 aa1eb5696..5a2747471 100644
--- a/test/clj/rems/workflow/test_dynamic.clj
+++ b/test/clj/rems/workflow/test_dynamic.clj
@@ -3,7 +3,7 @@
             [clojure.test :refer :all]
             [rems.application.events :as events]
             [rems.application.model :as model]
-            [rems.util :refer [assert-ex getx]]
+            [rems.util :refer [getx]]
             [rems.workflow.dynamic :as dynamic])
   (:import [java.util UUID]
            [org.joda.time DateTime]))
@@ -29,6 +29,13 @@
   (events/validate-events events)
   (dynamic/apply-events application events))
 
+(defmacro assert-ex
+  "Like assert but throw the result with ex-info and not as string. "
+  ([x message]
+   `(when-not ~x
+      (throw (ex-info (str "Assert failed: " ~message "\n" (pr-str '~x))
+                      (merge ~message {:expression '~x}))))))
+
 (defn- fail-command
   ([application cmd]
    (fail-command application cmd nil))