Repository: CSCfi/rems
Pull request #1495 (2019-08-15 08:48:42)

Add rems.api.services.catalogue


Patch:
diff --git a/src/clj/rems/api/applications.clj b/src/clj/rems/api/applications.clj
index f7091e50f..99b5bbe22 100644
--- a/src/clj/rems/api/applications.clj
+++ b/src/clj/rems/api/applications.clj
@@ -67,6 +67,42 @@
 
 ;; Api implementation
 
+(defn invalid-user? [u]
+  (or (str/blank? (:eppn u))
+      (str/blank? (:commonName u))
+      (str/blank? (:mail u))))
+
+;; XXX: Adding these attributes is not done consistently when retrieving
+;;   the data for a user.
+(defn format-user [u]
+  {:userid (:eppn u)
+   :name (:commonName u)
+   :email (:mail u)})
+
+;; TODO Filter applicant, requesting user
+;;
+;; XXX: Removing invalid users is not done consistently. It seems that
+;;   only the following API calls are affected:
+;;
+;;     /applications/commenters
+;;     /applications/members
+;;     /applications/deciders
+;;     /workflows/actors
+;;
+;;   For example, a user without commonName is able to log in and send an
+;;   application, and the application is visible to the handler and can
+;;   be approved.
+(defn get-users []
+  (->> (users/get-all-users)
+       (remove invalid-user?)
+       (map format-user)))
+
+(def get-applicants get-users)
+
+(def get-commenters get-users)
+
+(def get-deciders get-users)
+
 (def ^:private todo-roles
   #{:handler :commenter :decider :past-commenter :past-decider})
 
@@ -177,19 +213,19 @@
       :summary "Available third party commenters"
       :roles #{:handler}
       :return Commenters
-      (ok (users/get-commenters)))
+      (ok (get-commenters)))
 
     (GET "/members" []
       :summary "Existing REMS users available for application membership"
       :roles #{:handler}
       :return [Applicant]
-      (ok (users/get-applicants)))
+      (ok (get-applicants)))
 
     (GET "/deciders" []
       :summary "Available deciders"
       :roles #{:handler}
       :return Deciders
-      (ok (users/get-deciders)))
+      (ok (get-deciders)))
 
     (GET "/attachment/:attachment-id" []
       :summary "Get an attachment"
diff --git a/src/clj/rems/api/catalogue_items.clj b/src/clj/rems/api/catalogue_items.clj
index c1d5a04d0..05e219116 100644
--- a/src/clj/rems/api/catalogue_items.clj
+++ b/src/clj/rems/api/catalogue_items.clj
@@ -2,8 +2,8 @@
   (:require [clojure.string :as str]
             [compojure.api.sweet :refer :all]
             [rems.api.schema :refer :all]
+            [rems.api.services.catalogue :as catalogue]
             [rems.api.util :refer [not-found-json-response check-user]] ; required for route :roles
-            [rems.db.catalogue :as catalogue]
             [rems.db.core :as db]
             [ring.util.http-response :refer :all]
             [schema.core :as s]))
diff --git a/src/clj/rems/api/services/catalogue.clj b/src/clj/rems/api/services/catalogue.clj
new file mode 100644
index 000000000..c79d5bedc
--- /dev/null
+++ b/src/clj/rems/api/services/catalogue.clj
@@ -0,0 +1,24 @@
+(ns rems.api.services.catalogue
+  (:require [clojure.core.memoize :as memo]
+            [rems.common-util :refer [index-by]]
+            [rems.db.core :as db]
+            [rems.db.catalogue :as catalogue]))
+
+(defn create-catalogue-item! [command]
+  (let [id (:id (db/create-catalogue-item! (select-keys command [:title :form :resid :wfid :enabled :archived])))]
+    {:success (not (nil? id))
+     :id id}))
+
+(defn create-catalogue-item-localization! [command]
+  (let [return {:success (not (nil? (:id (db/create-catalogue-item-localization! (select-keys command [:id :langcode :title])))))}]
+    ;; Reset cache so that next call to get localizations will get this one.
+    (catalogue/reset-cache!)
+    return))
+
+(defn update-catalogue-item! [command]
+  ;; TODO disallow unarchiving catalogue item if its resource, form or licenses are archived
+  (db/set-catalogue-item-state! (select-keys command [:id :enabled :archived]))
+  {:success true})
+
+(def get-localized-catalogue-items catalogue/get-localized-catalogue-items)
+(def get-localized-catalogue-item catalogue/get-localized-catalogue-item)
diff --git a/src/clj/rems/api/services/workflow.clj b/src/clj/rems/api/services/workflow.clj
deleted file mode 100644
index dd5b1be66..000000000
--- a/src/clj/rems/api/services/workflow.clj
+++ /dev/null
@@ -1,51 +0,0 @@
-(ns rems.api.services.workflow
-  (:require [rems.db.catalogue :as catalogue]
-            [rems.db.core :as db]
-            [rems.db.workflow :as workflow]
-            [rems.db.users :as users]
-            [rems.json :as json])
-  (:import [org.apache.commons.lang3 NotImplementedException]))
-
-(defn- create-auto-approve-workflow! [{:keys [user-id organization title]}]
-  (assert user-id)
-  ;; TODO: create a new auto-approve workflow in the style of dynamic workflows
-  (throw (NotImplementedException. "auto-approve workflows are not yet implemented")))
-
-(defn- create-dynamic-workflow! [{:keys [user-id organization title handlers]}]
-  (assert user-id)
-  (assert organization)
-  (assert title)
-  (assert (every? string? handlers) {:handlers handlers})
-  (let [wfid (:id (db/create-workflow! {:organization organization,
-                                        :owneruserid user-id,
-                                        :modifieruserid user-id,
-                                        :title title,
-                                        :workflow (json/generate-string {:type :workflow/dynamic
-                                                                         :handlers handlers})}))]
-    {:id wfid}))
-
-(defn create-workflow! [command]
-  (let [result (case (:type command)
-                 :auto-approve (create-auto-approve-workflow! command)
-                 :dynamic (create-dynamic-workflow! command))]
-    (merge
-     result
-     {:success (not (nil? (:id result)))})))
-
-(defn update-workflow! [command]
-  (let [catalogue-items
-        (->> (catalogue/get-localized-catalogue-items {:workflow (:id command) :archived false})
-             (map #(select-keys % [:id :title :localizations])))]
-    (if (and (:archived command) (seq catalogue-items))
-      {: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})})))
-        {:success true}))))
-
-(defn get-workflow [id] (workflow/get-workflow id))
-(defn get-workflows [filters] (workflow/get-workflows filters))
-(defn get-available-actors [] (users/get-users))
diff --git a/src/clj/rems/api/workflows.clj b/src/clj/rems/api/workflows.clj
index 8197b430f..6ce90b00e 100644
--- a/src/clj/rems/api/workflows.clj
+++ b/src/clj/rems/api/workflows.clj
@@ -1,13 +1,48 @@
 (ns rems.api.workflows
-  (:require [compojure.api.sweet :refer :all]
-            [rems.api.applications :refer [User]]
+  (:require [clj-time.core :as time-core]
+            [clojure.test :refer [deftest is]]
+            [compojure.api.sweet :refer :all]
+            [rems.api.applications :refer [User get-users format-user]]
             [rems.api.schema :refer [SuccessResponse UserId Workflow]]
-            [rems.api.services.workflow :as workflow]
             [rems.api.util] ; required for route :roles
             [rems.db.core :as db]
-            [rems.util :refer [getx-user-id]]
+            [rems.db.users :as users]
+            [rems.db.workflow :as workflow]
+            [rems.util :refer [getx-user-id update-present]]
             [ring.util.http-response :refer :all]
-            [schema.core :as s]))
+            [schema.core :as s])
+  (:import [org.joda.time DateTime DateTimeZone]))
+
+(defn parse-db-time [s]
+  (when s
+    (-> (DateTime/parse s)
+        (.withZone DateTimeZone/UTC))))
+
+(deftest test-parse-db-time
+  (is (= nil (parse-db-time nil)))
+  (is (= (time-core/date-time 2019 1 30 7 56 38 627) (parse-db-time "2019-01-30T09:56:38.627616+02:00")))
+  (is (= (time-core/date-time 2015 2 13 12 47 26) (parse-db-time "2015-02-13T14:47:26+02:00"))))
+
+(defn format-license [license]
+  (-> license
+      (select-keys [:type :textcontent :localizations])
+      (assoc :start (parse-db-time (:start license)))
+      (assoc :end (parse-db-time (:end license)))))
+
+(defn- format-workflow
+  [{:keys [id organization owneruserid modifieruserid title workflow start end expired enabled archived licenses]}]
+  {:id id
+   :organization organization
+   :owneruserid owneruserid
+   :modifieruserid modifieruserid
+   :title title
+   :workflow workflow
+   :start start
+   :end end
+   :expired expired
+   :enabled enabled
+   :archived archived
+   :licenses (mapv format-license licenses)})
 
 (s/defschema CreateWorkflowCommand
   {:organization s/Str
@@ -30,6 +65,22 @@
 ; TODO: deduplicate or decouple with /api/applications/reviewers API?
 (s/defschema AvailableActor User)
 (s/defschema AvailableActors [AvailableActor])
+(def get-available-actors get-users) ;; TODO move get-users to rems.db.users
+
+(def ^:private get-user (comp format-user users/get-user-attributes))
+
+(defn- enrich-and-format-workflow [wf]
+  (-> wf
+      (update-present :workflow update :handlers #(mapv get-user %))
+      format-workflow))
+
+(defn- get-workflows [filters]
+  (mapv enrich-and-format-workflow (workflow/get-workflows filters)))
+
+(defn- get-workflow [workflow-id]
+  (-> workflow-id
+      workflow/get-workflow
+      enrich-and-format-workflow))
 
 (def workflows-api
   (context "/workflows" []
@@ -42,9 +93,9 @@
                      {expired :- (describe s/Bool "whether to include expired workflows") false}
                      {archived :- (describe s/Bool "whether to include archived workflows") false}]
       :return [Workflow]
-      (ok (workflow/get-workflows (merge (when-not expired {:expired false})
-                                         (when-not disabled {:enabled true})
-                                         (when-not archived {:archived false})))))
+      (ok (get-workflows (merge (when-not expired {:expired false})
+                                (when-not disabled {:enabled true})
+                                (when-not archived {:archived false})))))
 
     (POST "/create" []
       :summary "Create workflow"
@@ -64,11 +115,11 @@
       :summary "List of available actors"
       :roles #{:owner}
       :return AvailableActors
-      (ok (workflow/get-available-actors)))
+      (ok (get-available-actors)))
 
     (GET "/:workflow-id" []
       :summary "Get workflow by id"
       :roles #{:owner}
       :path-params [workflow-id :- (describe s/Int "workflow-id")]
       :return Workflow
-      (ok (workflow/get-workflow workflow-id)))))
+      (ok (get-workflow workflow-id)))))
diff --git a/src/clj/rems/application/model.clj b/src/clj/rems/application/model.clj
index ae914d5a6..42117c078 100644
--- a/src/clj/rems/application/model.clj
+++ b/src/clj/rems/application/model.clj
@@ -492,7 +492,7 @@
 (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 (mapv :userid (get-in workflow [:workflow :handlers])))]
+          handlers (set (get-in workflow [:workflow :handlers]))]
       (-> application
           (assoc-in [:application/workflow :workflow.dynamic/handlers] handlers)
           (permissions/give-role-to-users :handler handlers)))
diff --git a/src/clj/rems/db/catalogue.clj b/src/clj/rems/db/catalogue.clj
index 17ef225ea..1fc82b753 100644
--- a/src/clj/rems/db/catalogue.clj
+++ b/src/clj/rems/db/catalogue.clj
@@ -3,22 +3,22 @@
             [rems.common-util :refer [index-by]]
             [rems.db.core :as db]))
 
-(def +localizations-cache-time-ms+ (* 5 60 1000))
+(def ^:private +localizations-cache-time-ms+ (* 5 60 1000))
 
-(defn load-catalogue-item-localizations!
+(defn- load-catalogue-item-localizations!
   "Load catalogue item localizations from the database."
   []
   (->> (db/get-catalogue-item-localizations)
        (map #(update-in % [:langcode] keyword))
        (index-by [:id :langcode])))
 
-(defn get-cache [cache-key]
+(defn- get-cache [cache-key]
   (case cache-key
     :localizations (load-catalogue-item-localizations!)))
 
-(def cached (memo/ttl get-cache :ttl/threshold +localizations-cache-time-ms+))
+(def ^:private cached (memo/ttl get-cache :ttl/threshold +localizations-cache-time-ms+))
 
-(defn localize-catalogue-item
+(defn- localize-catalogue-item
   "Associates localisations into a catalogue item from
   the preloaded state."
   [item]
@@ -35,18 +35,5 @@
 (defn get-localized-catalogue-item [id]
   (first (get-localized-catalogue-items {:ids [id] :archived true :expand-names? true})))
 
-(defn create-catalogue-item! [command]
-  (let [id (:id (db/create-catalogue-item! (select-keys command [:title :form :resid :wfid :enabled :archived])))]
-    {:success (not (nil? id))
-     :id id}))
-
-(defn create-catalogue-item-localization! [command]
-  (let [return {:success (not (nil? (:id (db/create-catalogue-item-localization! (select-keys command [:id :langcode :title])))))}]
-    ;; Reset cache so that next call to get localizations will get this one.
-    (memo/memo-clear! cached)
-    return))
-
-(defn update-catalogue-item! [command]
-  ;; TODO disallow unarchiving catalogue item if its resource, form or licenses are archived
-  (db/set-catalogue-item-state! (select-keys command [:id :enabled :archived]))
-  {:success true})
+(defn reset-cache! []
+  (memo/memo-clear! cached))
diff --git a/src/clj/rems/db/users.clj b/src/clj/rems/db/users.clj
index 563ff26f9..c637d3fe0 100644
--- a/src/clj/rems/db/users.clj
+++ b/src/clj/rems/db/users.clj
@@ -1,20 +1,7 @@
 (ns rems.db.users
-  (:require [clojure.string :as str]
-            [rems.db.core :as db]
+  (:require [rems.db.core :as db]
             [rems.json :as json]))
 
-;; XXX: Adding these attributes is not done consistently when retrieving
-;;   the data for a user.
-(defn format-user [u]
-  {:userid (:eppn u)
-   :name (:commonName u)
-   :email (:mail u)})
-
-(defn- invalid-user? [u]
-  (or (str/blank? (:eppn u))
-      (str/blank? (:commonName u))
-      (str/blank? (:mail u))))
-
 (defn add-user! [user userattrs]
   (assert (and userattrs user) "User or user attributes missing!")
   (db/add-user! {:user user :userattrs (json/generate-string userattrs)}))
@@ -40,36 +27,7 @@
        (map get-user-attributes)
        (doall)))
 
-;; TODO Filter applicant, requesting user
-;;
-;; XXX: Removing invalid users is not done consistently. It seems that
-;;   only the following API calls are affected:
-;;
-;;     /applications/commenters
-;;     /applications/members
-;;     /applications/deciders
-;;     /workflows/actors
-;;
-;;   For example, a user without commonName is able to log in and send an
-;;   application, and the application is visible to the handler and can
-;;   be approved.
-(defn get-users []
-  (->> (get-all-users)
-       (remove invalid-user?)
-       (map format-user)))
-
-(def get-applicants get-users)
-
-(def get-commenters get-users)
-
-(def get-deciders get-users)
-
 (defn get-users-with-role [role]
   (->> (db/get-users-with-role {:role (name role)})
        (map :userid)
        (doall)))
-
-(defn get-user [userid]
-  (->> userid
-       get-user-attributes
-       format-user))
diff --git a/src/clj/rems/db/workflow.clj b/src/clj/rems/db/workflow.clj
index 2033d1302..447114896 100644
--- a/src/clj/rems/db/workflow.clj
+++ b/src/clj/rems/db/workflow.clj
@@ -1,49 +1,8 @@
 (ns rems.db.workflow
-  (:require [clj-time.core :as time-core]
-            [clojure.test :refer [deftest is]]
-            [rems.db.catalogue :as catalogue]
+  (:require [rems.db.catalogue :as catalogue]
             [rems.db.core :as db]
-            [rems.db.users :as users]
-            [rems.json :as json]
-            [rems.util :refer [update-present]])
-  (:import [org.joda.time DateTime DateTimeZone]))
-
-(defn- parse-db-time [s]
-  (when s
-    (-> (DateTime/parse s)
-        (.withZone DateTimeZone/UTC))))
-
-(deftest test-parse-db-time
-  (is (= nil (parse-db-time nil)))
-  (is (= (time-core/date-time 2019 1 30 7 56 38 627) (parse-db-time "2019-01-30T09:56:38.627616+02:00")))
-  (is (= (time-core/date-time 2015 2 13 12 47 26) (parse-db-time "2015-02-13T14:47:26+02:00"))))
-
-;; TODO: This should probably be in rems.db.licenses.
-(defn- format-license [license]
-  (-> license
-      (select-keys [:type :textcontent :localizations])
-      (assoc :start (parse-db-time (:start license)))
-      (assoc :end (parse-db-time (:end license)))))
-
-(defn- format-workflow
-  [{:keys [id organization owneruserid modifieruserid title workflow start end expired enabled archived licenses]}]
-  {:id id
-   :organization organization
-   :owneruserid owneruserid
-   :modifieruserid modifieruserid
-   :title title
-   :workflow workflow
-   :start start
-   :end end
-   :expired expired
-   :enabled enabled
-   :archived archived
-   :licenses (mapv format-license licenses)})
-
-(defn- enrich-and-format-workflow [wf]
-  (-> wf
-      (update-present :workflow update :handlers #(mapv users/get-user %))
-      format-workflow))
+            [rems.json :as json])
+  (:import [org.apache.commons.lang3 NotImplementedException]))
 
 (defn- parse-workflow-body [json]
   (json/parse-string json))
@@ -56,12 +15,50 @@
       db/get-workflow
       (update :workflow parse-workflow-body)
       (update :licenses parse-licenses)
-      db/assoc-expired
-      enrich-and-format-workflow))
+      db/assoc-expired))
 
 (defn get-workflows [filters]
   (->> (db/get-workflows)
        (map #(update % :workflow parse-workflow-body))
        (map db/assoc-expired)
-       (db/apply-filters filters)
-       (mapv enrich-and-format-workflow)))
+       (db/apply-filters filters)))
+
+(defn- create-auto-approve-workflow! [{:keys [user-id organization title]}]
+  (assert user-id)
+  ;; TODO: create a new auto-approve workflow in the style of dynamic workflows
+  (throw (NotImplementedException. "auto-approve workflows are not yet implemented")))
+
+(defn- create-dynamic-workflow! [{:keys [user-id organization title handlers]}]
+  (assert user-id)
+  (assert organization)
+  (assert title)
+  (assert (every? string? handlers) {:handlers handlers})
+  (let [wfid (:id (db/create-workflow! {:organization organization,
+                                        :owneruserid user-id,
+                                        :modifieruserid user-id,
+                                        :title title,
+                                        :workflow (json/generate-string {:type :workflow/dynamic
+                                                                         :handlers handlers})}))]
+    {:id wfid}))
+
+(defn create-workflow! [command]
+  (let [result (case (:type command)
+                 :auto-approve (create-auto-approve-workflow! command)
+                 :dynamic (create-dynamic-workflow! command))]
+    (merge
+     result
+     {:success (not (nil? (:id result)))})))
+
+(defn update-workflow! [command]
+  (let [catalogue-items
+        (->> (catalogue/get-localized-catalogue-items {:workflow (:id command) :archived false})
+             (map #(select-keys % [:id :title :localizations])))]
+    (if (and (:archived command) (seq catalogue-items))
+      {: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})})))
+        {:success true}))))


Test patch:
diff --git a/src/clj/rems/db/test_data.clj b/src/clj/rems/db/test_data.clj
index 730d23f86..b67f56556 100644
--- a/src/clj/rems/db/test_data.clj
+++ b/src/clj/rems/db/test_data.clj
@@ -1,61 +1,19 @@
 (ns rems.db.test-data
   "Populating the database with nice test data."
   (:require [clj-time.core :as time]
+            [rems.api.services.catalogue :as catalogue]
             [rems.api.services.licenses :as licenses]
             [rems.api.services.resource :as resource]
-            [rems.api.services.workflow :as workflow]
             [rems.db.applications :as applications]
-            [rems.db.catalogue :as catalogue]
             [rems.db.core :as db]
             [rems.db.form :as form]
             [rems.db.roles :as roles]
             [rems.db.users :as users]
+            [rems.db.workflow :as workflow]
             [rems.poller.entitlements :as entitlements-poller]
             [ring.util.http-response :refer [bad-request!]])
   (:import [java.util UUID]))
 
-;;; test data definitions
-
-(def +fake-users+
-  {:applicant1 "alice"
-   :applicant2 "malice"
-   :approver1 "developer"
-   :approver2 "bob"
-   :owner "owner"
-   :reporter "reporter"
-   :reviewer "carl"
-   :roleless1 "elsa"
-   :roleless2 "frank"})
-
-(def +fake-user-data+
-  {"developer" {:eppn "developer" :mail "developer@example.com" :commonName "Developer"}
-   "alice" {:eppn "alice" :mail "alice@example.com" :commonName "Alice Applicant"}
-   "malice" {:eppn "malice" :mail "malice@example.com" :commonName "Malice Applicant" :twinOf "alice" :other "Attribute Value"}
-   "bob" {:eppn "bob" :mail "bob@example.com" :commonName "Bob Approver"}
-   "carl" {:eppn "carl" :mail "carl@example.com" :commonName "Carl Reviewer"}
-   "elsa" {:eppn "elsa" :mail "elsa@example.com" :commonName "Elsa Roleless"}
-   "frank" {:eppn "frank" :mail "frank@example.com" :commonName "Frank Roleless"}
-   "owner" {:eppn "owner" :mail "owner@example.com" :commonName "Owner"}
-   "reporter" {:eppn "reporter" :mail "reporter@example.com" :commonName "Reporter"}})
-
-(def +demo-users+
-  {:applicant1 "RDapplicant1@funet.fi"
-   :applicant2 "RDapplicant2@funet.fi"
-   :approver1 "RDapprover1@funet.fi"
-   :approver2 "RDapprover2@funet.fi"
-   :reviewer "RDreview@funet.fi"
-   :owner "RDowner@funet.fi"
-   :reporter "RDdomainreporter@funet.fi"})
-
-(def +demo-user-data+
-  {"RDapplicant1@funet.fi" {:eppn "RDapplicant1@funet.fi" :mail "RDapplicant1.test@test_example.org" :commonName "RDapplicant1 REMSDEMO1"}
-   "RDapplicant2@funet.fi" {:eppn "RDapplicant2@funet.fi" :mail "RDapplicant2.test@test_example.org" :commonName "RDapplicant2 REMSDEMO"}
-   "RDapprover1@funet.fi" {:eppn "RDapprover1@funet.fi" :mail "RDapprover1.test@rems_example.org" :commonName "RDapprover1 REMSDEMO"}
-   "RDapprover2@funet.fi" {:eppn "RDapprover2@funet.fi" :mail "RDapprover2.test@rems_example.org" :commonName "RDapprover2 REMSDEMO"}
-   "RDreview@funet.fi" {:eppn "RDreview@funet.fi" :mail "RDreview.test@rems_example.org" :commonName "RDreview REMSDEMO"}
-   "RDowner@funet.fi" {:eppn "RDowner@funet.fi" :mail "RDowner.test@test_example.org" :commonName "RDowner REMSDEMO"}
-   "RDdomainreporter@funet.fi" {:eppn "RDdomainreporter@funet.fi" :mail "RDdomainreporter.test@test_example.org" :commonName "RDdomainreporter REMSDEMO"}})
-
 ;;; helpers for generating test data
 
 (defn command! [command]
@@ -74,12 +32,6 @@
                  (assoc-in m [k2 k1] v))
                {})))
 
-(defn create-user! [user-attributes & roles]
-  (let [user (:eppn user-attributes)]
-    (users/add-user! user user-attributes)
-    (doseq [role roles]
-      (roles/add-role! user role))))
-
 (defn create-license! [{:keys [actor]
                         :license/keys [type title link text]
                         :as command}]
@@ -114,15 +66,11 @@
 
 (defn create-dynamic-workflow! [{:keys [actor organization title handlers]
                                  :as command}]
-  (let [result (workflow/create-workflow!
-                {:user-id (or actor "owner")
-                 :organization (or organization "abc")
-                 :title (or title "")
-                 :type :dynamic
-                 :handlers
-                 (or handlers
-                     (do (create-user! (get +fake-user-data+ "developer"))
-                         ["developer"]))})]
+  (let [result (workflow/create-workflow! {:user-id (or actor "owner")
+                                           :organization (or organization "abc")
+                                           :title (or title "")
+                                           :type :dynamic
+                                           :handlers (or handlers ["developer"])})]
     (assert (:success result) {:command command :result result})
     (:id result)))
 
@@ -182,7 +130,53 @@
                        :actor actor})
     app-id))
 
-;;; generate test data
+;;; test data
+
+(def +fake-users+
+  {:applicant1 "alice"
+   :applicant2 "malice"
+   :approver1 "developer"
+   :approver2 "bob"
+   :owner "owner"
+   :reporter "reporter"
+   :reviewer "carl"
+   :roleless1 "elsa"
+   :roleless2 "frank"})
+
+(def +fake-user-data+
+  {"developer" {:eppn "developer" :mail "developer@example.com" :commonName "Developer"}
+   "alice" {:eppn "alice" :mail "alice@example.com" :commonName "Alice Applicant"}
+   "malice" {:eppn "malice" :mail "malice@example.com" :commonName "Malice Applicant" :twinOf "alice" :other "Attribute Value"}
+   "bob" {:eppn "bob" :mail "bob@example.com" :commonName "Bob Approver"}
+   "carl" {:eppn "carl" :mail "carl@example.com" :commonName "Carl Reviewer"}
+   "elsa" {:eppn "elsa" :mail "elsa@example.com" :commonName "Elsa Roleless"}
+   "frank" {:eppn "frank" :mail "frank@example.com" :commonName "Frank Roleless"}
+   "owner" {:eppn "owner" :mail "owner@example.com" :commonName "Owner"}
+   "reporter" {:eppn "reporter" :mail "reporter@example.com" :commonName "Reporter"}})
+
+(def +demo-users+
+  {:applicant1 "RDapplicant1@funet.fi"
+   :applicant2 "RDapplicant2@funet.fi"
+   :approver1 "RDapprover1@funet.fi"
+   :approver2 "RDapprover2@funet.fi"
+   :reviewer "RDreview@funet.fi"
+   :owner "RDowner@funet.fi"
+   :reporter "RDdomainreporter@funet.fi"})
+
+(def +demo-user-data+
+  {"RDapplicant1@funet.fi" {:eppn "RDapplicant1@funet.fi" :mail "RDapplicant1.test@test_example.org" :commonName "RDapplicant1 REMSDEMO1"}
+   "RDapplicant2@funet.fi" {:eppn "RDapplicant2@funet.fi" :mail "RDapplicant2.test@test_example.org" :commonName "RDapplicant2 REMSDEMO"}
+   "RDapprover1@funet.fi" {:eppn "RDapprover1@funet.fi" :mail "RDapprover1.test@rems_example.org" :commonName "RDapprover1 REMSDEMO"}
+   "RDapprover2@funet.fi" {:eppn "RDapprover2@funet.fi" :mail "RDapprover2.test@rems_example.org" :commonName "RDapprover2 REMSDEMO"}
+   "RDreview@funet.fi" {:eppn "RDreview@funet.fi" :mail "RDreview.test@rems_example.org" :commonName "RDreview REMSDEMO"}
+   "RDowner@funet.fi" {:eppn "RDowner@funet.fi" :mail "RDowner.test@test_example.org" :commonName "RDowner REMSDEMO"}
+   "RDdomainreporter@funet.fi" {:eppn "RDdomainreporter@funet.fi" :mail "RDdomainreporter.test@test_example.org" :commonName "RDdomainreporter REMSDEMO"}})
+
+(defn- create-user! [user-attributes & roles]
+  (let [user (:eppn user-attributes)]
+    (users/add-user! user user-attributes)
+    (doseq [role roles]
+      (roles/add-role! user role))))
 
 (defn create-users-and-roles! []
   ;; users provided by the fake login
diff --git a/test/clj/rems/db/test_catalogue.clj b/test/clj/rems/api/services/test_catalogue.clj
similarity index 96%
rename from test/clj/rems/db/test_catalogue.clj
rename to test/clj/rems/api/services/test_catalogue.clj
index db4c798b7..321a6fc29 100644
--- a/test/clj/rems/db/test_catalogue.clj
+++ b/test/clj/rems/api/services/test_catalogue.clj
@@ -1,6 +1,6 @@
-(ns ^:integration rems.db.test-catalogue
+(ns ^:integration rems.api.services.test-catalogue
   (:require [clojure.test :refer :all]
-            [rems.db.catalogue :as catalogue]
+            [rems.api.services.catalogue :as catalogue]
             [rems.db.core :as db]
             [rems.db.test-data :as test-data]
             [rems.db.testing :refer [rollback-db-fixture test-db-fixture]]))
diff --git a/test/clj/rems/api/test_workflows.clj b/test/clj/rems/api/test_workflows.clj
index 0b33b81cb..96a5c1ae3 100644
--- a/test/clj/rems/api/test_workflows.clj
+++ b/test/clj/rems/api/test_workflows.clj
@@ -1,10 +1,10 @@
 (ns ^:integration rems.api.test-workflows
   (:require [clojure.test :refer :all]
-            [rems.api.services.workflow :as workflow]
             [rems.api.testing :refer :all]
             [rems.common-util :refer [index-by]]
             [rems.db.test-data :as test-data]
             [rems.db.testing :refer [sync-with-database-time]]
+            [rems.db.workflow :as workflow]
             [rems.handler :refer [handler]]
             [ring.mock.request :refer :all]))
 
diff --git a/test/clj/rems/application/test_commands.clj b/test/clj/rems/application/test_commands.clj
index 55fd0a6dd..f429a4dad 100644
--- a/test/clj/rems/application/test_commands.clj
+++ b/test/clj/rems/application/test_commands.clj
@@ -24,9 +24,7 @@
                                     :form/id 1
                                     :workflow/id 1
                                     :workflow/type :workflow/dynamic})
-(def ^:private dummy-workflows {1 {:workflow {:handlers [{:userid handler-user-id
-                                                          :name "user"
-                                                          :email "user@example.com"}]}}})
+(def ^:private dummy-workflows {1 {:workflow {:handlers [handler-user-id]}}})
 (def ^:private dummy-forms {1 {}})
 
 (def ^:private injections
diff --git a/test/clj/rems/application/test_model.clj b/test/clj/rems/application/test_model.clj
index 8716edb5c..5148dacbb 100644
--- a/test/clj/rems/application/test_model.clj
+++ b/test/clj/rems/application/test_model.clj
@@ -176,9 +176,7 @@
        :organization "org"
        :title "workflow title"
        :workflow {:type "workflow/dynamic"
-                  :handlers [{:userid "handler"
-                              :name "Handler"
-                              :email "handler@example.com"}]}
+                  :handlers ["handler"]}
        :licenses nil
        :owneruserid "owner"
        :modifieruserid "owner"
diff --git a/test/clj/rems/db/test_entitlements.clj b/test/clj/rems/db/test_entitlements.clj
index 22411fdc5..f3b918888 100644
--- a/test/clj/rems/db/test_entitlements.clj
+++ b/test/clj/rems/db/test_entitlements.clj
@@ -100,9 +100,9 @@
                                                           :license-ids [lic-id1]})
                 :form-id form-id
                 :workflow-id wfid})]
-    (test-data/create-user! {:eppn applicant :mail "b@o.b" :commonName "Bob"})
-    (test-data/create-user! {:eppn member :mail "e.l@s.a" :commonName "Elsa"})
-    (test-data/create-user! {:eppn admin :mail "o.w@n.er" :commonName "Owner"})
+    (db/add-user! {:user applicant :userattrs (cheshire/generate-string {"mail" "b@o.b"})})
+    (db/add-user! {:user member :userattrs (cheshire/generate-string {"mail" "e.l@s.a"})})
+    (db/add-user! {:user admin :userattrs nil})
 
     (let [app-id (test-data/create-application! {:actor applicant :catalogue-item-ids [item1 item2]})]
       (testing "submitted application should not yet cause entitlements"
diff --git a/test/clj/rems/poller/test_email.clj b/test/clj/rems/poller/test_email.clj
index 3f5f5719c..dcb22a07a 100644
--- a/test/clj/rems/poller/test_email.clj
+++ b/test/clj/rems/poller/test_email.clj
@@ -53,12 +53,7 @@
                             :title "fi title 21"}}}})
 
 (def ^:private get-workflow
-  {5 {:workflow {:handlers [{:userid "handler"
-                             :name "Handler"
-                             :email "handler@example.com"}
-                            {:userid "assistant"
-                             :name "Assistant"
-                             :email "assistant@example.com"}]}}})
+  {5 {:workflow {:handlers ["handler" "assistant"]}}})
 
 (defn ^:private get-nothing [& _]
   nil)
diff --git a/test/clj/rems/test_db.clj b/test/clj/rems/test_db.clj
index 374e383ea..57f8b65ec 100644
--- a/test/clj/rems/test_db.clj
+++ b/test/clj/rems/test_db.clj
@@ -37,8 +37,8 @@
           "should find same catalogue item by id"))))
 
 (deftest test-multi-applications
-  (test-data/create-user! {:eppn "test-user" :mail "test-user@test.com" :commonName "Test-user"})
-  (test-data/create-user! {:eppn "handler" :mail "handler@test.com" :commonName "Handler"})
+  (db/add-user! {:user "test-user" :userattrs nil})
+  (db/add-user! {:user "handler" :userattrs nil})
   (let [applicant "test-user"
         wfid (test-data/create-dynamic-workflow! {:handlers ["handler"]})
         res1 (test-data/create-resource! {:resource-ext-id "resid111"})
@@ -81,9 +81,9 @@
   (is (thrown? RuntimeException (roles/add-role! "pekka" :unknown-role))))
 
 (deftest test-get-entitlements-for-export
-  (test-data/create-user! {:eppn "handler" :mail "handler@test.com" :commonName "Handler"})
-  (test-data/create-user! {:eppn "jack" :mail "jack@test.com" :commonName "Jack"})
-  (test-data/create-user! {:eppn "jill" :mail "jill@test.com" :commonName "Jill"})
+  (db/add-user! {:user "handler" :userattrs nil})
+  (db/add-user! {:user "jack" :userattrs nil})
+  (db/add-user! {:user "jill" :userattrs nil})
   (let [wf (test-data/create-dynamic-workflow! {:handlers ["handler"]})
         form-id (test-data/create-form! {})
         res1 (test-data/create-resource! {:resource-ext-id "resource1"})