diff --git a/backend/src/app/loggers/webhooks.clj b/backend/src/app/loggers/webhooks.clj index 28331540f5..a5849c6da2 100644 --- a/backend/src/app/loggers/webhooks.clj +++ b/backend/src/app/loggers/webhooks.clj @@ -22,11 +22,11 @@ ;; --- PROC -(defn lookup-webhooks-by-team +(defn- lookup-webhooks-by-team [pool team-id] (db/exec! pool ["select * from webhook where team_id=? and is_active=true" team-id])) -(defn lookup-webhooks-by-project +(defn- lookup-webhooks-by-project [pool project-id] (let [sql [(str "select * from webhook as w" " join project as p on (p.team_id = w.team_id)" @@ -34,7 +34,7 @@ project-id]] (db/exec! pool sql))) -(defn lookup-webhooks-by-file +(defn- lookup-webhooks-by-file [pool file-id] (let [sql [(str "select * from webhook as w" " join project as p on (p.team_id = w.team_id)" @@ -43,7 +43,7 @@ file-id]] (db/exec! pool sql))) -(defn lookup-webhooks +(defn- lookup-webhooks [{:keys [::db/pool]} {:keys [props] :as event}] (or (some->> (:team-id props) (lookup-webhooks-by-team pool)) (some->> (:project-id props) (lookup-webhooks-by-project pool)) @@ -77,7 +77,7 @@ (declare interpret-exception) (declare interpret-response) -(def ^:private mapper +(def ^:private json-mapper (json/mapper {:encode-key-fn str/camel :decode-key-fn (comp keyword str/kebab) @@ -123,7 +123,7 @@ whook (::config props) body (case (:mtype whook) - "application/json" (json/encode-str event mapper) + "application/json" (json/encode-str event json-mapper) "application/transit+json" (t/encode-str event) "application/x-www-form-urlencoded" (uri/map->query-string event))] diff --git a/backend/src/app/rpc/commands/webhooks.clj b/backend/src/app/rpc/commands/webhooks.clj index f1af961758..fdbc30851b 100644 --- a/backend/src/app/rpc/commands/webhooks.clj +++ b/backend/src/app/rpc/commands/webhooks.clj @@ -99,8 +99,8 @@ {::doc/added "1.17"} [{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [profile-id team-id] :as params}] (check-edition-permissions! pool profile-id team-id) - (->> (validate-webhook! cfg nil params) - (p/fmap executor (fn [_] (validate-quotes! cfg params))) + (->> (validate-quotes! cfg params) + (p/fmap executor (fn [_] (validate-webhook! cfg nil params))) (p/fmap executor (fn [_] (insert-webhook! cfg params))))) (s/def ::update-webhook diff --git a/backend/test/backend_tests/helpers.clj b/backend/test/backend_tests/helpers.clj index 1ab57e5231..41cf3e1cfa 100644 --- a/backend/test/backend_tests/helpers.clj +++ b/backend/test/backend_tests/helpers.clj @@ -284,6 +284,19 @@ :session-id session-id :profile-id profile-id}))))) +(defn create-webhook* + ([params] (create-webhook* *pool* params)) + ([pool {:keys [team-id id uri mtype is-active] + :or {is-active true + mtype "application/json" + uri "http://example.com/webhook"}}] + (db/insert! pool :webhook + {:id (or id (uuid/next)) + :team-id team-id + :uri uri + :is-active is-active + :mtype mtype}))) + ;; --- RPC HELPERS (defn handle-error @@ -417,6 +430,10 @@ [& params] (apply db/query *pool* params)) +(defn db-get + [& params] + (apply db/get* *pool* params)) + (defn sleep [ms-or-duration] (Thread/sleep (inst-ms (dt/duration ms-or-duration)))) diff --git a/backend/test/backend_tests/loggers_webhooks_test.clj b/backend/test/backend_tests/loggers_webhooks_test.clj new file mode 100644 index 0000000000..8af5bd780b --- /dev/null +++ b/backend/test/backend_tests/loggers_webhooks_test.clj @@ -0,0 +1,120 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns backend-tests.loggers-webhooks-test + (:require + [app.common.uuid :as uuid] + [app.db :as db] + [app.http :as http] + [app.storage :as sto] + [backend-tests.helpers :as th] + [clojure.test :as t] + [mockery.core :refer [with-mocks]])) + +(t/use-fixtures :once th/state-init) +(t/use-fixtures :each th/database-reset) + +(t/deftest process-event-handler-with-no-webhooks + (with-mocks [submit-mock {:target 'app.worker/submit! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + res (th/run-task! :process-webhook-event + {:props + {:app.loggers.webhooks/event + {:type "mutation" + :name "create-project" + :props {:team-id (:default-team-id prof)}}}})] + + (t/is (= 0 (:call-count @submit-mock))) + (t/is (nil? res))))) + +(t/deftest process-event-handler + (with-mocks [submit-mock {:target 'app.worker/submit! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + whk (th/create-webhook* {:team-id (:default-team-id prof)}) + res (th/run-task! :process-webhook-event + {:props + {:app.loggers.webhooks/event + {:type "mutation" + :name "create-project" + :props {:team-id (:default-team-id prof)}}}})] + + (t/is (= 1 (:call-count @submit-mock))) + (t/is (nil? res))))) + +(t/deftest run-webhook-handler-1 + (with-mocks [http-mock {:target 'app.http.client/req! :return {:status 200}}] + (let [prof (th/create-profile* 1 {:is-active true}) + whk (th/create-webhook* {:team-id (:default-team-id prof)}) + evt {:type "mutation" + :name "create-project" + :props {:team-id (:default-team-id prof)}} + res (th/run-task! :run-webhook + {:props + {:app.loggers.webhooks/event evt + :app.loggers.webhooks/config whk}})] + + (t/is (= 1 (:call-count @http-mock))) + + (let [rows (th/db-exec! ["select * from webhook_delivery where webhook_id=?" + (:id whk)])] + (t/is (= 1 (count rows))) + (t/is (nil? (-> rows first :error-code)))) + + ;; Refresh webhook + (let [whk' (th/db-get :webhook {:id (:id whk)})] + (t/is (nil? (:error-code whk'))) + (prn whk')) + + ))) + +(t/deftest run-webhook-handler-2 + (with-mocks [http-mock {:target 'app.http.client/req! :return {:status 400}}] + (let [prof (th/create-profile* 1 {:is-active true}) + whk (th/create-webhook* {:team-id (:default-team-id prof)}) + evt {:type "mutation" + :name "create-project" + :props {:team-id (:default-team-id prof)}} + res (th/run-task! :run-webhook + {:props + {:app.loggers.webhooks/event evt + :app.loggers.webhooks/config whk}})] + + (t/is (= 1 (:call-count @http-mock))) + + (let [rows (th/db-query :webhook-delivery {:webhook-id (:id whk)})] + (t/is (= 1 (count rows))) + (t/is (= "unexpected-status:400" (-> rows first :error-code)))) + + ;; Refresh webhook + (let [whk' (th/db-get :webhook {:id (:id whk)})] + (t/is (= "unexpected-status:400" (:error-code whk'))) + (t/is (= 1 (:error-count whk')))) + + + ;; RUN 2 times more + + (th/run-task! :run-webhook + {:props + {:app.loggers.webhooks/event evt + :app.loggers.webhooks/config whk}}) + + (th/run-task! :run-webhook + {:props + {:app.loggers.webhooks/event evt + :app.loggers.webhooks/config whk}}) + + + (let [rows (th/db-query :webhook-delivery {:webhook-id (:id whk)})] + (t/is (= 3 (count rows))) + (t/is (= "unexpected-status:400" (-> rows first :error-code)))) + + ;; Refresh webhook + (let [whk' (th/db-get :webhook {:id (:id whk)})] + (t/is (= "unexpected-status:400" (:error-code whk'))) + (t/is (= 3 (:error-count whk'))) + (t/is (false? (:is-active whk')))) + + ))) diff --git a/backend/test/backend_tests/rpc_webhooks_test.clj b/backend/test/backend_tests/rpc_webhooks_test.clj index 37c1c83a0b..6d2f97a170 100644 --- a/backend/test/backend_tests/rpc_webhooks_test.clj +++ b/backend/test/backend_tests/rpc_webhooks_test.clj @@ -12,8 +12,6 @@ [app.storage :as sto] [backend-tests.helpers :as th] [clojure.test :as t] - [datoteka.fs :as fs] - [datoteka.io :as io] [mockery.core :refer [with-mocks]])) (t/use-fixtures :once th/state-init) @@ -52,7 +50,6 @@ (t/is (= (:mtype params) (:mtype result))) (vreset! whook result)))) - (th/reset-mock! http-mock) (t/testing "update webhook 1 (success)" @@ -144,3 +141,41 @@ (t/is (= (:code error-data) :object-not-found))))) ))) + +(t/deftest webhooks-quotes + (with-mocks [http-mock {:target 'app.http.client/req! + :return {:status 200}}] + + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + params {::th/type :create-webhook + :profile-id (:id prof) + :team-id team-id + :uri "http://example.com" + :mtype "application/json"} + out1 (th/command! params) + out2 (th/command! params) + out3 (th/command! params) + out4 (th/command! params) + out5 (th/command! params) + out6 (th/command! params) + out7 (th/command! params) + out8 (th/command! params) + out9 (th/command! params)] + + (t/is (= 8 (:call-count @http-mock))) + + (t/is (nil? (:error out1))) + (t/is (nil? (:error out2))) + (t/is (nil? (:error out3))) + (t/is (nil? (:error out4))) + (t/is (nil? (:error out5))) + (t/is (nil? (:error out6))) + (t/is (nil? (:error out7))) + (t/is (nil? (:error out8))) + + (let [error (:error out9) + error-data (ex-data error)] + (t/is (th/ex-info? error)) + (t/is (= (:type error-data) :restriction)) + (t/is (= (:code error-data) :webhooks-quote-reached))))))