Repository: CSCfi/rems
Pull request #1098 (2019-04-09 14:29:42)

Member entitlements
Implements granting entitlements for members after licenses are accepted and application approved from #653. Entitlements are also removed when a member is removed or the application is closed. The entitlement tests are moved into their own proper places.


Patch:
diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql
index 64061e46d..2065a6c7f 100644
--- a/resources/sql/queries.sql
+++ b/resources/sql/queries.sql
@@ -342,7 +342,11 @@ VALUES (:application, :user, :resource);
 -- :name end-entitlement! :!
 UPDATE entitlement
 SET endt = current_timestamp
-WHERE catAppId = :application;
+WHERE catAppId = :application
+/*~ (when (:user params) */
+  AND entitlement.userId = :user
+/*~ ) ~*/
+;
 
 -- :name get-entitlements :?
 -- :doc
@@ -350,6 +354,7 @@ WHERE catAppId = :application;
 --   :application -- application id to limit select to
 --   :user -- user id to limit select to
 --   :resource -- resid to limit select to
+--   :is-active? -- entitlement is without end date
 SELECT res.resId, catAppId, entitlement.userId, entitlement.start, users.userAttrs->>'mail' AS mail FROM entitlement
 LEFT OUTER JOIN resource res ON entitlement.resId = res.id
 LEFT OUTER JOIN users on entitlement.userId = users.userId
@@ -363,6 +368,9 @@ WHERE 1=1
 /*~ (when (:resource params) */
   AND res.resId = :resource
 /*~ ) ~*/
+/*~ (when (:is-active? params) */
+  AND entitlement.endt IS NULL
+/*~ ) ~*/
 ;
 
 -- :name save-field-value! :!
diff --git a/src/clj/rems/api/schema.clj b/src/clj/rems/api/schema.clj
index 99720937b..65705e25c 100644
--- a/src/clj/rems/api/schema.clj
+++ b/src/clj/rems/api/schema.clj
@@ -135,6 +135,8 @@
    :application/applicant-attributes {s/Keyword s/Str}
    :application/members #{{:userid s/Str
                            s/Keyword s/Str}}
+   :application/past-members #{{:userid s/Str
+                                s/Keyword s/Str}}
    :application/invited-members #{{:name s/Str
                                    :email s/Str}}
    :application/resources [V2Resource]
diff --git a/src/clj/rems/application/model.clj b/src/clj/rems/application/model.clj
index cdc48dc65..e2ffc90f2 100644
--- a/src/clj/rems/application/model.clj
+++ b/src/clj/rems/application/model.clj
@@ -52,6 +52,21 @@
                                       :decider [:see-everything
                                                 :application.command/decide]})
 
+(def ^:private approved-permissions {:applicant [:application.command/remove-member
+                                                 :application.command/uninvite-member
+                                                 :application.command/accept-licenses]
+                                     :member [:application.command/accept-licenses]
+                                     :handler [:see-everything
+                                               :application.command/add-member
+                                               :application.command/remove-member
+                                               :application.command/invite-member
+                                               :application.command/uninvite-member
+                                               :application.command/close]
+                                     :commenter [:see-everything
+                                                 :application.command/comment]
+                                     :decider [:see-everything
+                                               :application.command/decide]})
+
 (def ^:private closed-permissions {:applicant []
                                    :member []
                                    :handler [:see-everything]
@@ -116,12 +131,7 @@
 (defmethod calculate-permissions :application.event/approved
   [application _event]
   (-> application
-      (permissions/set-role-permissions (update closed-permissions
-                                                :handler set/union #{:application.command/add-member
-                                                                     :application.command/remove-member
-                                                                     :application.command/invite-member
-                                                                     :application.command/uninvite-member
-                                                                     :application.command/close}))))
+      (permissions/set-role-permissions approved-permissions)))
 
 (defmethod calculate-permissions :application.event/rejected
   [application _event]
@@ -159,6 +169,7 @@
              :application/modified (:event/time event)
              :application/applicant (:event/actor event)
              :application/members #{}
+             :application/past-members #{}
              :application/invitation-tokens {}
              :application/resources (map (fn [resource]
                                            {:catalogue-item/id (:catalogue-item/id resource)
@@ -167,7 +178,7 @@
              :application/licenses (map (fn [license]
                                           {:license/id (:license/id license)})
                                         (:application/licenses event))
-             :application/accepted-licenses {"applicant" #{}}
+             :application/accepted-licenses {}
              :application/events []
              :application/form {:form/id (:form/id event)}
              :application/workflow {:workflow/id (:workflow/id event)
@@ -218,7 +229,8 @@
 (defmethod event-type-specific-application-view :application.event/member-removed
   [application event]
   (-> application
-      (update :application/members disj (:application/member event))))
+      (update :application/members disj (:application/member event))
+      (update :application/past-members conj (:application/member event))))
 
 (defmethod event-type-specific-application-view :application.event/submitted
   [application event]
diff --git a/src/clj/rems/db/entitlements.clj b/src/clj/rems/db/entitlements.clj
index a2d4dec74..b97a774e0 100644
--- a/src/clj/rems/db/entitlements.clj
+++ b/src/clj/rems/db/entitlements.clj
@@ -4,6 +4,7 @@
             [clojure.string :refer [join]]
             [clojure.tools.logging :as log]
             [rems.auth.util :refer [throw-forbidden]]
+            [rems.application-util :refer [accepted-licenses?]]
             [rems.config :refer [env]]
             [rems.db.core :as db]
             [rems.json :as json]
@@ -66,26 +67,46 @@
   "If the given application is approved, add an entitlement to the db
   and call the entitlement REST callback (if defined)."
   [application]
-  ;; TODO this is not idempotent
   (when (= :application.state/approved (:application/state application))
     (let [app-id (:application/id application)
-          user-id (:application/applicant application)]
-      (log/info "granting entitlements on application" app-id "to" user-id)
-      (doseq [resource-id (->> (:application/resources application)
-                               (map :resource/id))]
-        (db/add-entitlement! {:application app-id
-                              :user user-id
-                              :resource resource-id}))
-      (post-entitlements :add (db/get-entitlements {:application app-id})))))
+          members (conj (map :userid (:application/members application))
+                        (:application/applicant application))
+          has-entitlement? (set (map :userid (db/get-entitlements {:application app-id})))
+          members-to-update (->> members
+                                 (filter #(accepted-licenses? application %))
+                                 (remove has-entitlement?))]
+      (when (seq members-to-update)
+        (doseq [user-id members-to-update]
+          (log/info "granting entitlements on application" app-id "to" user-id)
+          (doseq [[resource-id ext-id] (->> (:application/resources application)
+                                            (map (juxt :resource/id :resource/ext-id)))]
+            (db/add-entitlement! {:application app-id
+                                  :user user-id
+                                  :resource resource-id})
+            (post-entitlements :add (db/get-entitlements {:application app-id :user user-id :resource ext-id})))))
+      members-to-update)))
 
 (defn- end-entitlements-for
   [application]
-  (when (= :application.state/closed (:application/state application))
-    (let [app-id (:application/id application)
-          user-id (:application/applicant application)]
-      (log/info "ending entitlements on application" app-id "to" user-id)
-      (db/end-entitlement! {:application app-id})
-      (post-entitlements :remove (db/get-entitlements {:application app-id})))))
+  (let [app-id (:application/id application)
+        members (case (:application/state application)
+                    :application.state/closed
+                    (conj (map :userid (:application/members application))
+                          (:application/applicant application))
+
+                    :application.state/approved
+                    (map :userid (:application/past-members application)) ; someone was removed
+
+                    []) ; by default don't do anything
+        has-entitlement? (set (map :userid (db/get-entitlements {:application app-id})))
+        members-to-update (->> members
+                               (filter has-entitlement?))]
+    (when (seq members-to-update)
+      (doseq [user-id members-to-update]
+        (log/info "ending entitlements on application" app-id "to" user-id)
+        (db/end-entitlement! {:application app-id :user user-id})
+        (post-entitlements :remove (db/get-entitlements {:application app-id :user user-id}))))
+    members-to-update))
 
 (defn update-entitlements-for
   [application]
diff --git a/src/clj/rems/poller/entitlements.clj b/src/clj/rems/poller/entitlements.clj
index 29ea65922..7037f8842 100644
--- a/src/clj/rems/poller/entitlements.clj
+++ b/src/clj/rems/poller/entitlements.clj
@@ -10,9 +10,12 @@
 
 (defn- entitlements-for-event [event]
   ;; we filter by event here, and by state in update-entitlements-for.
-  ;; this is because update-entitlements-for is not actually
-  ;; idempotent.
-  (when (contains? #{:application.event/approved :application.event/closed} (:event/type event))
+  ;; this is for performance reasons only
+  (when (contains? #{:application.event/approved
+                     :application.event/licenses-accepted
+                     :application.event/member-removed
+                     :application.event/closed}
+                   (:event/type event))
     (let [application (applications-v2/get-unrestricted-application (:application/id event))]
       (entitlements/update-entitlements-for application))))
 
diff --git a/src/cljc/rems/application_util.cljc b/src/cljc/rems/application_util.cljc
index 302ec84db..ce7aa7a2f 100644
--- a/src/cljc/rems/application_util.cljc
+++ b/src/cljc/rems/application_util.cljc
@@ -1,5 +1,12 @@
 (ns rems.application-util)
 
+(defn accepted-licenses? [application userid]
+  (let [application-licenses (map :license/id (:application/licenses application))
+        user-accepted-licenses (get (:application/accepted-licenses application) userid)]
+    (cond (empty? application-licenses) true
+          (empty? user-accepted-licenses) false
+          :else (every? (set user-accepted-licenses) application-licenses))))
+
 (defn form-fields-editable? [application]
   (contains? (or (:possible-commands application) ;; TODO: remove v1 api usage
                  (:application/permissions application))
diff --git a/src/cljs/rems/application.cljs b/src/cljs/rems/application.cljs
index aae9dbfe8..5e75a155b 100644
--- a/src/cljs/rems/application.cljs
+++ b/src/cljs/rems/application.cljs
@@ -14,7 +14,7 @@
             [rems.actions.request-comment :refer [request-comment-action-button request-comment-form]]
             [rems.actions.request-decision :refer [request-decision-action-button request-decision-form]]
             [rems.actions.return-action :refer [return-action-button return-form]]
-            [rems.application-util :refer [form-fields-editable?]]
+            [rems.application-util :refer [accepted-licenses? form-fields-editable?]]
             [rems.atoms :refer [external-link flash-message info-field readonly-checkbox textarea]]
             [rems.catalogue-util :refer [get-catalogue-item-title]]
             [rems.collapsible :as collapsible]
@@ -41,12 +41,6 @@
                     :application.state/closed}
                   (:application/state application))))
 
-(defn- accepted-licenses? [application userid]
-  (every? (or (get (:application/accepted-licenses application)
-                   userid)
-              #{})
-          (map :license/id (:application/licenses application))))
-
 (defn- disabled-items-warning [application]
   (when (in-processing? application)
     (when-some [resources (->> (:application/resources application)


Test patch:
diff --git a/src/clj/rems/db/test_data.clj b/src/clj/rems/db/test_data.clj
index 2b199e6de..2c7a7a124 100644
--- a/src/clj/rems/db/test_data.clj
+++ b/src/clj/rems/db/test_data.clj
@@ -483,13 +483,20 @@
              update :field-values
              conj [item-id trimmed-value]))
     (db/update-application-description! {:id app-id :description field-value})
-    (doseq [{license-id :id} (:licenses form)]
-      (db/save-license-approval! {:catappid app-id
-                                  :round 0
-                                  :licid license-id
-                                  :actoruserid user-id
-                                  :state "approved"}))
+    (when-not dynamic-workflow?
+      (doseq [{license-id :id} (:licenses form)]
+        (db/save-license-approval! {:catappid app-id
+                                    :round 0
+                                    :licid license-id
+                                    :actoruserid user-id
+                                    :state "approved"})))
     (when dynamic-workflow?
+      (let [error (applications/command! {:type :application.command/accept-licenses
+                                          :actor user-id
+                                          :application-id app-id
+                                          :accepted-licenses (map :id (:licenses form))
+                                          :time (time/now)})]
+        (assert (nil? error) error))
       (let [error (applications/command! @save-draft-command)]
         (assert (nil? error) error)))
     app-id))
diff --git a/test/clj/rems/api/test_applications.clj b/test/clj/rems/api/test_applications.clj
index 9a38bc83a..f790c219c 100644
--- a/test/clj/rems/api/test_applications.clj
+++ b/test/clj/rems/api/test_applications.clj
@@ -178,6 +178,7 @@
       (let [application (get-application application-id user-id)]
         (is (= "workflow/dynamic" (get-in application [:application/workflow :workflow/type])))
         (is (= ["application.event/created"
+                "application.event/licenses-accepted"
                 "application.event/draft-saved"
                 "application.event/submitted"]
                (map :event/type (get application :application/events))))
@@ -267,6 +268,7 @@
                     :application/state "application.state/approved"}
                    (select-keys handler-data [:application/id :application/state])))
             (is (= ["application.event/created"
+                    "application.event/licenses-accepted"
                     "application.event/draft-saved"
                     "application.event/submitted"
                     "application.event/comment-requested"
@@ -277,6 +279,7 @@
                    handler-event-types)))
           (testing "applicant cannot see all events"
             (is (= ["application.event/created"
+                    "application.event/licenses-accepted"
                     "application.event/draft-saved"
                     "application.event/submitted"
                     "application.event/approved"]
diff --git a/test/clj/rems/api/test_entitlements.clj b/test/clj/rems/api/test_entitlements.clj
index 8769bae06..bba4004fd 100644
--- a/test/clj/rems/api/test_entitlements.clj
+++ b/test/clj/rems/api/test_entitlements.clj
@@ -2,6 +2,7 @@
   (:require [clojure.test :refer :all]
             [rems.handler :refer [handler]]
             [rems.api.testing :refer :all]
+            [rems.db.users :as users]
             [ring.mock.request :refer :all]))
 
 (use-fixtures
@@ -14,3 +15,44 @@
                        handler)
           body (read-body response)]
       (is (= "unauthorized" body)))))
+
+(deftest entitlements-test
+  (rems.poller.entitlements/run)
+  (let [api-key "42"]
+    (testing "all"
+      (let [data (-> (request :get "/api/entitlements")
+                     (authenticate api-key "developer")
+                     handler
+                     read-body)]
+        (is (= 2 (count data)))))
+
+    (testing "just for alice"
+      (let [data (-> (request :get "/api/entitlements?user=alice")
+                     (authenticate api-key "developer")
+                     handler
+                     read-body)]
+        (is (= 1 (count data)))
+        ;; sanity check the data
+        (is (= {:resource "urn:nbn:fi:lb-201403262"
+                :application-id 12
+                :mail "alice@example.com"}
+               (-> data first (dissoc :start))))))
+
+    (testing "listing as applicant"
+      (testing "with entitlements"
+        (let [body (-> (request :get (str "/api/entitlements"))
+                       (authenticate api-key "alice")
+                       handler
+                       assert-response-is-ok
+                       read-body)]
+          (is (coll-is-not-empty? body))
+          (is (every? #(= (:mail %) "alice@example.com") body))))
+
+      (testing "without entitlements"
+        (users/add-user! "allison" {})
+        (let [body (-> (request :get (str "/api/entitlements"))
+                       (authenticate api-key "allison")
+                       handler
+                       assert-response-is-ok
+                       read-body)]
+          (is (coll-is-empty? body)))))))
diff --git a/test/clj/rems/api/test_public.clj b/test/clj/rems/api/test_public.clj
index fd6bbf88d..ab1725038 100644
--- a/test/clj/rems/api/test_public.clj
+++ b/test/clj/rems/api/test_public.clj
@@ -1,9 +1,7 @@
 (ns ^:integration rems.api.test-public
   (:require [clojure.test :refer :all]
             [rems.api.testing :refer :all]
-            [rems.db.users :as users]
             [rems.handler :refer :all]
-            [rems.poller.entitlements]
             [ring.mock.request :refer :all]))
 
 (use-fixtures
@@ -19,39 +17,3 @@
                    read-body)
           languages (keys data)]
       (is (= [:en :fi] (sort languages))))))
-
-(deftest entitlements-test
-  (rems.poller.entitlements/run)
-  (let [api-key "42"]
-    (testing "all"
-      (let [data (-> (request :get "/api/entitlements")
-                     (authenticate api-key "developer")
-                     handler
-                     read-body)]
-        (is (= 2 (count data)))))
-
-    (testing "just for alice"
-      (let [data (-> (request :get "/api/entitlements?user=alice")
-                     (authenticate api-key "developer")
-                     handler
-                     read-body)]
-        (is (= 1 (count data)))))
-
-    (testing "listing as applicant"
-      (testing "with entitlements"
-        (let [body (-> (request :get (str "/api/entitlements"))
-                       (authenticate api-key "alice")
-                       handler
-                       assert-response-is-ok
-                       read-body)]
-          (is (coll-is-not-empty? body))
-          (is (every? #(= (:mail %) "alice@example.com") body))))
-
-      (testing "without entitlements"
-        (users/add-user! "allison" {})
-        (let [body (-> (request :get (str "/api/entitlements"))
-                       (authenticate api-key "allison")
-                       handler
-                       assert-response-is-ok
-                       read-body)]
-          (is (coll-is-empty? body)))))))
diff --git a/test/clj/rems/application/test_model.clj b/test/clj/rems/application/test_model.clj
index 005fd0522..ddd00f1a9 100644
--- a/test/clj/rems/application/test_model.clj
+++ b/test/clj/rems/application/test_model.clj
@@ -152,6 +152,7 @@
                                                                      :mail "applicant@example.com"
                                                                      :commonName "Applicant"}
                                   :application/members #{}
+                                  :application/past-members #{}
                                   :application/invitation-tokens {}
                                   :application/resources [{:catalogue-item/id 10
                                                            :resource/id 11
@@ -217,7 +218,7 @@
                                                           :license/expired false
                                                           :license/enabled true
                                                           :license/archived false}]
-                                  :application/accepted-licenses {"applicant" #{}}
+                                  :application/accepted-licenses {}
                                   :application/events events
                                   :application/description ""
                                   :application/form {:form/id 40
@@ -257,7 +258,7 @@
                                                   :application/last-activity (DateTime. 2000)
                                                   :application/events events
                                                   :application/description "foo"
-                                                  :application/accepted-licenses {"applicant" #{}}
+                                                  :application/accepted-licenses {}
                                                   :application/form {:form/fields [{:field/value "foo"}
                                                                                    {:field/value "bar"}]}})]
             (is (= expected-application (apply-events events)))
@@ -526,7 +527,8 @@
                               expected-application (merge expected-application
                                                           {:application/last-activity (DateTime. 5000)
                                                            :application/events events
-                                                           :application/members #{}})]
+                                                           :application/members #{}
+                                                           :application/past-members #{{:userid "member"}}})]
                           (is (= expected-application (apply-events events))))))))))))))))
 
 (deftest test-calculate-permissions
diff --git a/test/clj/rems/db/test_entitlements.clj b/test/clj/rems/db/test_entitlements.clj
index c90f9367c..b2ef23f6a 100644
--- a/test/clj/rems/db/test_entitlements.clj
+++ b/test/clj/rems/db/test_entitlements.clj
@@ -1,14 +1,22 @@
-(ns rems.db.test-entitlements
+(ns ^:integration rems.db.test-entitlements
   (:require [cheshire.core :as cheshire]
             [clj-time.core :as time]
             [clojure.test :refer :all]
             [rems.db.core :as db]
+            [rems.db.applications :as applications]
             [rems.db.entitlements :as entitlements]
+            [rems.db.licenses :as licenses]
+            [rems.db.resource :as resource]
+            [rems.db.testing :refer [test-db-fixture rollback-db-fixture test-data-fixture]]
             [rems.testing-util :refer [suppress-logging]]
             [stub-http.core :as stub]))
 
-(use-fixtures :once
-  (suppress-logging "rems.db.entitlements"))
+(use-fixtures
+  :once
+  (suppress-logging "rems.db.entitlements")
+  test-db-fixture
+  rollback-db-fixture
+  test-data-fixture)
 
 (def +entitlements+
   [{:resid "res1" :catappid 11 :userid "user1" :start (time/date-time 2001 10 11) :mail "user1@tes.t"}
@@ -59,3 +67,163 @@
           (let [[{payload :payload status :status}] @log]
             (is (= "exception" status))
             (is (= +expected-payload+ (cheshire/parse-string payload)))))))))
+
+(deftest test-entitlement-granting
+  (rems.poller.entitlements/run) ; clear previous entitlements
+  (with-open [server (stub/start! {"/add" {:status 200}
+                                   "/remove" {:status 200}})]
+    (with-redefs [rems.config/env {:entitlements-target
+                                   {:add (str (:uri server) "/add")
+                                    :remove (str (:uri server) "/remove")}}]
+      (let [uid "bob"
+            memberid "elsa"
+            admin "owner"
+            organization "foo"
+            workflow {:type :workflow/dynamic :handlers [admin]}
+            wfid (:id (db/create-workflow! {:organization "abc" :modifieruserid "owner" :owneruserid "owner" :title "dynamic" :fnlround -1 :workflow (cheshire/generate-string workflow)}))
+            formid (:id (db/create-form! {:organization "abc" :title "internal-title" :user "owner"}))
+            lic-id1 (:id (licenses/create-license! {:licensetype "text"
+                                                    :title "license1"
+                                                    :textcontent "license1 text"
+                                                    :localizations {}}
+                                                   "owner"))
+            lic-id2 (:id (licenses/create-license! {:licensetype "text"
+                                                    :title "license2"
+                                                    :textcontent "license2 text"
+                                                    :localizations {}}
+                                                   "owner"))
+            res1 (:id (resource/create-resource! {:resid "resource1" :organization organization :licenses [lic-id1]} "owner"))
+            res2 (:id (resource/create-resource! {:resid "resource2" :organization organization :licenses [lic-id2]} "owner"))
+            item1 (:id (db/create-catalogue-item! {:title "item1" :form formid :resid res1 :wfid wfid}))
+            item2 (:id (db/create-catalogue-item! {:title "item2" :form formid :resid res2 :wfid wfid}))]
+        (db/add-user! {:user uid :userattrs (cheshire/generate-string {"mail" "b@o.b"})})
+        (db/add-user! {:user memberid :userattrs (cheshire/generate-string {"mail" "e.l@s.a"})})
+        (db/add-user! {:user admin :userattrs nil})
+        (let [app-id (:application-id (applications/create-application! uid [item1 item2]))]
+          (is (nil? (applications/command! {:type :application.command/submit
+                                            :actor uid
+                                            :application-id app-id
+                                            :time (time/now)})))
+          (is (nil? (applications/command! {:type :application.command/add-member
+                                            :actor admin
+                                            :application-id app-id
+                                            :member {:userid memberid}
+                                            :time (time/now)})))
+          (testing "submitted application should not yet cause entitlements"
+            (rems.poller.entitlements/run)
+            (is (empty? (db/get-entitlements {:application app-id})))
+            (is (empty? (stub/recorded-requests server))))
+
+          (is (nil? (applications/command! {:type :application.command/approve
+                                            :actor admin
+                                            :application-id app-id
+                                            :comment ""
+                                            :time (time/now)})))
+
+          (testing "approved application should not yet cause entitlements"
+            (rems.poller.entitlements/run)
+            (is (empty? (db/get-entitlements {:application app-id})))
+            (is (empty? (stub/recorded-requests server))))
+
+          (is (nil? (applications/command! {:type :application.command/accept-licenses
+                                            :actor uid
+                                            :application-id app-id
+                                            :accepted-licenses [lic-id1 lic-id2]
+                                            :time (time/now)})))
+
+          (is (nil? (applications/command! {:type :application.command/accept-licenses
+                                            :actor memberid
+                                            :application-id app-id
+                                            :accepted-licenses [lic-id1] ; only accept some licenses
+                                            :time (time/now)})))
+
+          (is (= {uid #{lic-id1 lic-id2}
+                  memberid #{lic-id1}}
+                 (:application/accepted-licenses (applications/get-application-state app-id))))
+
+          (testing "approved application, licenses accepted by one user generates entitlements for that user"
+            (rems.poller.entitlements/run)
+            (rems.poller.entitlements/run) ;; run twice to check idempotence
+            (is (= 2 (count (stub/recorded-requests server))))
+            (testing "db"
+              (is (= [[uid "resource1"] [uid "resource2"]]
+                     (map (juxt :userid :resid) (db/get-entitlements {:application app-id})))))
+            (testing "POST"
+              (let [data (take 2 (stub/recorded-requests server))
+                    targets (map :path data)
+                    bodies (->> data
+                                (map #(get-in % [:body "postData"]))
+                                (map #(cheshire/parse-string % keyword))
+                                (apply concat))]
+                (is (every? #{"/add"} targets))
+                (is (= [{:resource "resource1" :application app-id :user "bob" :mail "b@o.b"}
+                        {:resource "resource2" :application app-id :user "bob" :mail "b@o.b"}]
+                       bodies)))))
+
+          (is (nil? (applications/command! {:type :application.command/accept-licenses
+                                            :actor memberid
+                                            :application-id app-id
+                                            :accepted-licenses [lic-id1 lic-id2] ; now accept all licenses
+                                            :time (time/now)})))
+
+          (testing "approved application, more accepted licenses generates more entitlements"
+            (rems.poller.entitlements/run)
+            (is (= 4 (count (stub/recorded-requests server))))
+            (testing "db"
+              (is (= [[uid "resource1"] [uid "resource2"]
+                      [memberid "resource1"] [memberid "resource2"]]
+                     (map (juxt :userid :resid) (db/get-entitlements {:application app-id})))))
+            (testing "POST"
+              (let [data (take 2 (drop 2 (stub/recorded-requests server)))
+                    targets (map :path data)
+                    bodies (->> data
+                                (map #(get-in % [:body "postData"]))
+                                (map #(cheshire/parse-string % keyword))
+                                (apply concat))]
+                (is (every? #{"/add"} targets))
+                (is (= [{:resource "resource1" :application app-id :user "elsa" :mail "e.l@s.a"}
+                        {:resource "resource2" :application app-id :user "elsa" :mail "e.l@s.a"}]
+                       bodies)))))
+
+          (is (nil? (applications/command! {:type :application.command/remove-member
+                                            :actor admin
+                                            :application-id app-id
+                                            :member {:userid memberid}
+                                            :comment "Left team"
+                                            :time (time/now)})))
+
+          (testing "removing a member ends entitlements"
+            (rems.poller.entitlements/run)
+            (is (= 5 (count (stub/recorded-requests server))))
+            (testing "db"
+              (is (= [[uid "resource1"] [uid "resource2"]]
+                     (map (juxt :userid :resid) (db/get-entitlements {:application app-id :is-active? true})))))
+            (testing "POST"
+              (let [data (nth (stub/recorded-requests server) 4)
+                    target (:path data)
+                    body (cheshire/parse-string (get-in data [:body "postData"]) keyword)]
+                (is (= "/remove" target))
+                (is (= [{:resource "resource1" :application app-id :user "elsa" :mail "e.l@s.a"}
+                        {:resource "resource2" :application app-id :user "elsa" :mail "e.l@s.a"}]
+                       body)))))
+
+          (is (nil? (applications/command! {:type :application.command/close
+                                            :actor admin
+                                            :application-id app-id
+                                            :comment ""
+                                            :time (time/now)})))
+
+          (testing "closed application should end entitlements"
+            (rems.poller.entitlements/run)
+            (is (= 6 (count (stub/recorded-requests server))))
+            (testing "db"
+              (is (= []
+                     (map (juxt :userid :resid) (db/get-entitlements {:application app-id :is-active? true})))))
+            (testing "POST"
+              (let [data (nth (stub/recorded-requests server) 5)
+                    target (:path data)
+                    body (cheshire/parse-string (get-in data [:body "postData"]) keyword)]
+                (is (= "/remove" target))
+                (is (= [{:resource "resource1" :application app-id :user "bob" :mail "b@o.b"}
+                        {:resource "resource2" :application app-id :user "bob" :mail "b@o.b"}]
+                       body))))))))))
diff --git a/test/clj/rems/test_db.clj b/test/clj/rems/test_db.clj
index 172ca60bf..30ab2fae4 100644
--- a/test/clj/rems/test_db.clj
+++ b/test/clj/rems/test_db.clj
@@ -669,75 +669,6 @@
       (is (thrown? ForbiddenException
                    (entitlements/get-entitlements-for-export))))))
 
-(deftest test-entitlement-granting
-  (with-open [server (stub/start! {"/add" {:status 200}
-                                   "/remove" {:status 200}})]
-    (with-redefs [rems.config/env {:entitlements-target
-                                   {:add (str (:uri server) "/add")
-                                    :remove (str (:uri server) "/remove")}}]
-      (let [uid "bob"
-            admin "owner"
-            organization "foo"
-            workflow {:type :workflow/dynamic :handlers [admin]}
-            wfid (:id (db/create-workflow! {:organization "abc" :modifieruserid "owner" :owneruserid "owner" :title "dynamic" :fnlround -1 :workflow (cheshire/generate-string workflow)}))
-            formid (:id (db/create-form! {:organization "abc" :title "internal-title" :user "owner"}))
-            res1 (:id (db/create-resource! {:resid "resource1" :organization organization :owneruserid admin :modifieruserid admin}))
-            res2 (:id (db/create-resource! {:resid "resource2" :organization organization :owneruserid admin :modifieruserid admin}))
-            item1 (:id (db/create-catalogue-item! {:title "item1" :form formid :resid res1 :wfid wfid}))
-            item2 (:id (db/create-catalogue-item! {:title "item2" :form formid :resid res2 :wfid wfid}))]
-        (db/add-user! {:user uid :userattrs (cheshire/generate-string {"mail" "b@o.b"})})
-        (db/add-user! {:user admin :userattrs nil})
-        (let [app-id (:application-id (applications/create-application! uid [item1 item2]))]
-          (is (nil? (applications/command! {:type :application.command/submit
-                                            :actor uid
-                                            :application-id app-id
-                                            :time (time/now)})))
-          (testing "submitted application should not yet cause entitlements"
-            (rems.poller.entitlements/run)
-            (is (empty? (db/get-entitlements {:application app-id})))
-            (is (empty? (stub/recorded-requests server))))
-
-          (is (nil? (applications/command! {:type :application.command/approve
-                                            :actor admin
-                                            :application-id app-id
-                                            :comment ""
-                                            :time (time/now)})))
-
-          (testing "approved application generated entitlements"
-            (rems.poller.entitlements/run)
-            (rems.poller.entitlements/run) ;; run twice to check idempotence
-            (testing "db"
-              (= [1 2]
-                 (db/get-entitlements {:application app-id})))
-            (testing "POST"
-              (let [data (first (stub/recorded-requests server))
-                    target (:path data)
-                    body (cheshire/parse-string (get-in data [:body "postData"]))]
-                (is (= "/add" target))
-                (is (= [{"resource" "resource1" "application" app-id "user" "bob" "mail" "b@o.b"}
-                        {"resource" "resource2" "application" app-id "user" "bob" "mail" "b@o.b"}]
-                       body)))))
-
-          (is (nil? (applications/command! {:type :application.command/close
-                                            :actor admin
-                                            :application-id app-id
-                                            :comment ""
-                                            :time (time/now)})))
-
-          (testing "closed application should end entitlements"
-            (rems.poller.entitlements/run)
-            (testing "db"
-              (= [1 2]
-                 (db/get-entitlements {:application app-id})))
-            (testing "POST"
-              (let [data (second (stub/recorded-requests server))
-                    target (:path data)
-                    body (cheshire/parse-string (get-in data [:body "postData"]))]
-                (is (= "/remove" target))
-                (is (= [{"resource" "resource1" "application" app-id "user" "bob" "mail" "b@o.b"}
-                        {"resource" "resource2" "application" app-id "user" "bob" "mail" "b@o.b"}]
-                       body))))))))))
-
 (deftest test-create-demo-data!
   ;; just a smoke test, check that create-demo-data doesn't fail
   (test-data/create-demo-data!)