Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial implementation of bona-fide-bot #2456

Merged
merged 11 commits into from
Dec 1, 2020
2 changes: 2 additions & 0 deletions src/clj/rems/api/services/command.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[clojure.tools.logging :as log]
[rems.api.services.blacklist :as blacklist]
[rems.application.approver-bot :as approver-bot]
[rems.application.bonafide-bot :as bonafide-bot]
[rems.application.commands :as commands]
[rems.application.rejecter-bot :as rejecter-bot]
[rems.common.application-util :as application-util]
Expand Down Expand Up @@ -43,6 +44,7 @@
(entitlements/update-entitlements-for-events new-events)
(rejecter-bot/run-rejecter-bot new-events)
(approver-bot/run-approver-bot new-events)
(bonafide-bot/run-bonafide-bot new-events)
(event-notification/queue-notifications! new-events)
(delete-applications new-events)))

Expand Down
46 changes: 46 additions & 0 deletions src/clj/rems/application/bonafide_bot.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
(ns rems.application.bonafide-bot
"A bot that enables workflows where a user can ask another user to vouch
for their bona fide researcher status."
(:require [clj-time.core :as time]
[rems.common.application-util :as application-util]
[rems.db.applications :as applications]
[rems.db.users :as users]))

(def bot-userid "bonafide-bot")

(defn- find-email-address [application]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add some protection about having just one email address? Or just use find-first to be more explicit about which to return?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be an assertion too I guess, but it's a bit impolite if it fails only when the bot tries to do something, not when creating the workflow

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a test

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah ideally there would be some collaboration with the form editor or you would pick the email field in the workflow/bot settings. Or this could be a report of its own, data validation sort of things. We don't have to do it now as long as the docs match the exact behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use field ids for this btw, fetching the unique field with id "referrer-email" or something?

Of course if there are multiple forms there might be multiple fields with the same field id, but we could use the first form.

(some (fn [field]
(when (= :email (:field/type field))
(:field/value field)))
(mapcat :form/fields
(:application/forms application))))

(defn- may-give-bonafide-status? [user-attributes]
(contains? #{"so" "system"} (:researcher-status-by user-attributes)))

(defn- generate-commands [event application]
(when (application-util/is-handler? application bot-userid)
(case (:event/type event)
:application.event/submitted
(let [email (find-email-address application)]
(assert email (pr-str application))
[{:type :application.command/invite-decider
:time (time/now)
:application-id (:application/id application)
:actor bot-userid
:decider {:name "Referer"
:email email}}])
:application.event/decided
(when (may-give-bonafide-status? (users/get-user (:event/actor event)))
[{:type (case (:application/decision event)
:approved :application.command/approve
:rejected :application.command/reject)
:time (time/now)
:application-id (:application/id application)
:actor bot-userid}])

[])))

(defn run-bonafide-bot [new-events]
(doall (mapcat #(generate-commands % (applications/get-application (:application/id %)))
new-events)))
96 changes: 96 additions & 0 deletions test/clj/rems/api/test_end_to_end.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
(:require [clojure.test :refer :all]
[rems.api.testing :refer :all]
[rems.application.approver-bot :as approver-bot]
[rems.application.bonafide-bot :as bonafide-bot]
[rems.application.rejecter-bot :as rejecter-bot]
[rems.db.applications :as applications]
[rems.db.entitlements :as entitlements]
[rems.email.core :as email]
[rems.event-notification :as event-notification]
Expand Down Expand Up @@ -607,3 +609,97 @@
(testing "and it doesn't get rejected"
(is (= "application.state/rejected" (:application/state (api-call :get (str "/api/applications/" app-3) nil
api-key applicant-id))))))))))

(deftest test-bonafide-bot
(let [api-key "42"
owner-id "owner"
applicant-id "bonafide-applicant"
applicant-attributes {:userid applicant-id
:name "Bona Fide Applicant"
:email "[email protected]"}
referer-id "bonafide-referer"
referer-attributes {:userid referer-id
:name "Bona Fide Referer"
:email "[email protected]"
:researcher-status-by "so"}
bot-attributes {:userid bonafide-bot/bot-userid
:email nil
:name "bonafide bot"}]
(testing "create users"
(api-call :post "/api/users/create" applicant-attributes api-key owner-id)
(api-call :post "/api/users/create" referer-attributes api-key owner-id)
(api-call :post "/api/users/create" bot-attributes api-key owner-id))
(let [resource-id
(extract-id
opqdonut marked this conversation as resolved.
Show resolved Hide resolved
(api-call :post "/api/resources/create" {:organization {:organization/id "default"}
:resid "bonafide"
:licenses []}
api-key owner-id))

form-id
(extract-id
(api-call :post "/api/forms/create" {:organization {:organization/id "default"}
:form/title "e2e"
:form/fields [{:field/type :email
:field/id "referer email"
:field/title {:en "referer"
:fi "referer"
:sv "referer"}
:field/optional false}]}
api-key owner-id))
workflow-id
(extract-id
(api-call :post "/api/workflows/create" {:organization {:organization/id "default"}
:title "bonafide workflow"
:type :workflow/default
:handlers [bonafide-bot/bot-userid]}
api-key owner-id))

catalogue-item-id
(extract-id
(api-call :post "/api/catalogue-items/create" {:organization {:organization/id "default"}
:resid resource-id
:form form-id
:wfid workflow-id
:localizations {:en {:title "bonafide catalogue item"}}}
api-key owner-id))
app-id
(testing "create application"
(:application-id
(assert-success
(api-call :post "/api/applications/create" {:catalogue-item-ids [catalogue-item-id]}
api-key applicant-id))))]
(testing "fill & submit application"
(assert-success
(api-call :post "/api/applications/save-draft" {:application-id app-id
:field-values [{:form form-id
:field "referer email"
:value "[email protected]"}]}
api-key applicant-id))
(assert-success
(api-call :post "/api/applications/submit" {:application-id app-id}
api-key applicant-id)))
(let [event (-> (applications/get-application-internal app-id)
:application/events
last)
token (:invitation/token event)]
(testing "check that bot has requested decision"
(is (= {:event/type :application.event/decider-invited
:application/id app-id
:application/decider {:name "Referer"
:email "[email protected]"}
:event/actor bonafide-bot/bot-userid}
(select-keys event [:event/type :application/id :application/decider :event/actor])))
(is (string? token)))
(testing "post decision as referer"
(assert-success
(api-call :post (str "/api/applications/accept-invitation?invitation-token=" token) nil
api-key referer-id))
(assert-success
(api-call :post "/api/applications/decide" {:application-id app-id
:decision :approved}
api-key referer-id)))
(testing "check that application was approved"
(let [application (api-call :get (str "/api/applications/" app-id) nil
api-key applicant-id)]
(is (= "application.state/approved" (:application/state application)))))))))