mirror of
https://github.com/penpot/penpot.git
synced 2026-08-29 16:18:48 +00:00
✨ Enable closed schemas for RPC methods
This commit is contained in:
parent
b5bec4f983
commit
0eed852e2e
@ -92,12 +92,12 @@
|
|||||||
(handle-response-transformation request mdata)
|
(handle-response-transformation request mdata)
|
||||||
(handle-before-comple-hook mdata))))
|
(handle-before-comple-hook mdata))))
|
||||||
|
|
||||||
(defn- make-rpc-handler
|
(defn make-rpc-handler
|
||||||
"Ring handler that dispatches cmd requests and convert between
|
"Ring handler that dispatches cmd requests and convert between
|
||||||
internal async flow into ring async flow."
|
internal async flow into ring async flow."
|
||||||
[methods]
|
[methods]
|
||||||
(let [methods (update-vals methods peek)]
|
(let [methods (update-vals methods peek)]
|
||||||
(fn [{:keys [params path-params method] :as request}]
|
(fn [{:keys [path-params method] :as request}]
|
||||||
(let [handler-name (:method-name path-params)
|
(let [handler-name (:method-name path-params)
|
||||||
etag (yreq/get-header request "if-none-match")
|
etag (yreq/get-header request "if-none-match")
|
||||||
session-id (yreq/get-header request "x-session-id")
|
session-id (yreq/get-header request "x-session-id")
|
||||||
@ -111,7 +111,7 @@
|
|||||||
|
|
||||||
ip-addr (inet/parse-request request)
|
ip-addr (inet/parse-request request)
|
||||||
|
|
||||||
data (-> params
|
data (-> {}
|
||||||
(assoc ::handler-name handler-name)
|
(assoc ::handler-name handler-name)
|
||||||
(assoc ::ip-addr ip-addr)
|
(assoc ::ip-addr ip-addr)
|
||||||
(assoc ::request-at (ct/now))
|
(assoc ::request-at (ct/now))
|
||||||
@ -218,17 +218,6 @@
|
|||||||
f)
|
f)
|
||||||
f))
|
f))
|
||||||
|
|
||||||
(defn- wrap-spec-conform
|
|
||||||
[_ f mdata]
|
|
||||||
;; NOTE: skip spec conform operation on rpc methods that already
|
|
||||||
;; uses malli validation mechanism.
|
|
||||||
(if (contains? mdata ::sm/params)
|
|
||||||
f
|
|
||||||
(if-let [spec (ex/ignoring (s/spec (::sv/spec mdata)))]
|
|
||||||
(fn [cfg params]
|
|
||||||
(f cfg (us/conform spec params)))
|
|
||||||
f)))
|
|
||||||
|
|
||||||
(defn- wrap-params-validation
|
(defn- wrap-params-validation
|
||||||
[_ f mdata]
|
[_ f mdata]
|
||||||
(if-let [schema (::sm/params mdata)]
|
(if-let [schema (::sm/params mdata)]
|
||||||
@ -237,16 +226,15 @@
|
|||||||
decode (sm/decoder schema sm/json-transformer)
|
decode (sm/decoder schema sm/json-transformer)
|
||||||
encode (sm/encoder schema sm/json-transformer)]
|
encode (sm/encoder schema sm/json-transformer)]
|
||||||
(fn [cfg params]
|
(fn [cfg params]
|
||||||
(let [params (decode params)]
|
(let [request-params (-> params meta ::http/request :params decode)]
|
||||||
(if (validate params)
|
(if (validate request-params)
|
||||||
(let [result (f cfg params)]
|
(let [result (f cfg (merge params request-params))]
|
||||||
(if (instance? clojure.lang.IObj result)
|
(if (instance? clojure.lang.IObj result)
|
||||||
(vary-meta result assoc :encode/json encode)
|
(vary-meta result assoc :encode/json encode)
|
||||||
result))
|
result))
|
||||||
(let [params (d/without-qualified params)]
|
(ex/raise :type :validation
|
||||||
(ex/raise :type :validation
|
:code :params-validation
|
||||||
:code :params-validation
|
::sm/explain (explain request-params))))))
|
||||||
::sm/explain (explain params)))))))
|
|
||||||
f))
|
f))
|
||||||
|
|
||||||
|
|
||||||
@ -334,10 +322,9 @@
|
|||||||
(wrap-metrics cfg $ mdata)
|
(wrap-metrics cfg $ mdata)
|
||||||
(rlimit/wrap cfg $ mdata)
|
(rlimit/wrap cfg $ mdata)
|
||||||
(wrap-audit cfg $ mdata)
|
(wrap-audit cfg $ mdata)
|
||||||
(wrap-spec-conform cfg $ mdata)
|
(wrap-nitrate-sso cfg $ mdata)
|
||||||
(wrap-params-validation cfg $ mdata)
|
(wrap-params-validation cfg $ mdata)
|
||||||
(wrap-authentication cfg $ mdata)
|
(wrap-authentication cfg $ mdata)))
|
||||||
(wrap-nitrate-sso cfg $ mdata)))
|
|
||||||
|
|
||||||
(defn- wrap-management
|
(defn- wrap-management
|
||||||
[cfg f mdata]
|
[cfg f mdata]
|
||||||
@ -347,12 +334,9 @@
|
|||||||
(climit/wrap cfg $ mdata)
|
(climit/wrap cfg $ mdata)
|
||||||
(wrap-metrics cfg $ mdata)
|
(wrap-metrics cfg $ mdata)
|
||||||
(wrap-audit cfg $ mdata)
|
(wrap-audit cfg $ mdata)
|
||||||
(wrap-spec-conform cfg $ mdata)
|
(wrap-nitrate-sso cfg $ mdata)
|
||||||
(wrap-params-validation cfg $ mdata)
|
(wrap-params-validation cfg $ mdata)
|
||||||
(wrap-authentication cfg $ mdata)
|
(wrap-authentication cfg $ mdata)))
|
||||||
(wrap-nitrate-sso cfg $ mdata)))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn- process-method
|
(defn- process-method
|
||||||
[cfg wrap-fn [f mdata]]
|
[cfg wrap-fn [f mdata]]
|
||||||
|
|||||||
@ -118,7 +118,7 @@
|
|||||||
|
|
||||||
(def ^:private schema:import-binfile
|
(def ^:private schema:import-binfile
|
||||||
[:and
|
[:and
|
||||||
[:map {:title "import-binfile"}
|
[:map {:title "import-binfile" :closed true}
|
||||||
[:name [:or [:string {:max 250}]
|
[:name [:or [:string {:max 250}]
|
||||||
[:map-of ::sm/uuid [:string {:max 250}]]]]
|
[:map-of ::sm/uuid [:string {:max 250}]]]]
|
||||||
[:project-id ::sm/uuid]
|
[:project-id ::sm/uuid]
|
||||||
|
|||||||
@ -49,7 +49,9 @@
|
|||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
[promesa.exec :as px]
|
[promesa.exec :as px]
|
||||||
[ring.core.protocols :as rcp]
|
[ring.core.protocols :as rcp]
|
||||||
|
[yetti.request :as yreq]
|
||||||
[yetti.request :as yrq]
|
[yetti.request :as yrq]
|
||||||
|
[yetti.response :as yres]
|
||||||
[yetti.response :as yres])
|
[yetti.response :as yres])
|
||||||
(:import
|
(:import
|
||||||
java.io.PipedInputStream
|
java.io.PipedInputStream
|
||||||
@ -379,6 +381,82 @@
|
|||||||
{:error (handle-error e#)
|
{:error (handle-error e#)
|
||||||
:result nil})))
|
:result nil})))
|
||||||
|
|
||||||
|
(defrecord DummyRequest [headers cookies method body-stream
|
||||||
|
remote-addr server-name server-port
|
||||||
|
scheme protocol path query ssl-client-cert]
|
||||||
|
yreq/IRequestCookies
|
||||||
|
(get-cookie [_ name]
|
||||||
|
{:value (get cookies name)})
|
||||||
|
|
||||||
|
yreq/IRequest
|
||||||
|
(get-header [_ name]
|
||||||
|
(get headers name))
|
||||||
|
(method [_] method)
|
||||||
|
(body [_] body-stream)
|
||||||
|
(path [_] path)
|
||||||
|
(query [_] query)
|
||||||
|
(server-port [_] server-port)
|
||||||
|
(server-name [_] server-name)
|
||||||
|
(remote-addr [_] remote-addr)
|
||||||
|
(ssl-client-cert [_] ssl-client-cert)
|
||||||
|
(scheme [_] scheme)
|
||||||
|
(protocol [_] protocol))
|
||||||
|
|
||||||
|
(defn make-dummy-request
|
||||||
|
"Constructs a DummyRequest from an options map. Every key is
|
||||||
|
optional; missing values fall back to sensible defaults. New
|
||||||
|
fields added to DummyRequest won't break existing call sites
|
||||||
|
as long as this constructor keeps its `:or` defaults in sync.
|
||||||
|
|
||||||
|
Recognized keys:
|
||||||
|
:headers — map of header name → value
|
||||||
|
:cookies — map of cookie name → value
|
||||||
|
:method — HTTP method keyword (default :get)
|
||||||
|
:body-stream — InputStream for the body (used directly)
|
||||||
|
:body-bytes — bytes or string for the body; wrapped in a
|
||||||
|
ByteArrayInputStream if :body-stream is not
|
||||||
|
given
|
||||||
|
:remote-addr — string (default \"127.0.0.1\")
|
||||||
|
:server-name — string (default \"test\")
|
||||||
|
:server-port — long (default 0)
|
||||||
|
:scheme — keyword (default :http)
|
||||||
|
:protocol — string (default \"HTTP/1.1\")
|
||||||
|
:path — string (default \"/test\")
|
||||||
|
:query — string or nil (default nil)
|
||||||
|
:ssl-client-cert — X509Certificate or nil (default nil)"
|
||||||
|
[& {:keys [headers cookies method body-stream body-bytes
|
||||||
|
remote-addr server-name server-port scheme protocol
|
||||||
|
path query ssl-client-cert]
|
||||||
|
:or {headers {} cookies {} method :get
|
||||||
|
body-stream nil
|
||||||
|
remote-addr "127.0.0.1" server-name "test" server-port 0
|
||||||
|
scheme :http protocol "HTTP/1.1" path "/test" query nil
|
||||||
|
ssl-client-cert nil}}]
|
||||||
|
(let [body-stream (or body-stream
|
||||||
|
(when body-bytes
|
||||||
|
(java.io.ByteArrayInputStream.
|
||||||
|
(if (string? body-bytes)
|
||||||
|
(.getBytes ^String body-bytes "UTF-8")
|
||||||
|
body-bytes))))]
|
||||||
|
(->DummyRequest headers cookies method body-stream
|
||||||
|
remote-addr server-name server-port
|
||||||
|
scheme protocol path query ssl-client-cert)))
|
||||||
|
|
||||||
|
(defn- prepare-rpc-params
|
||||||
|
[data]
|
||||||
|
(let [params (reduce-kv (fn [params k v]
|
||||||
|
(if (qualified-keyword? k)
|
||||||
|
(assoc params k v)
|
||||||
|
params))
|
||||||
|
{}
|
||||||
|
(dissoc data ::type))
|
||||||
|
request (-> (make-dummy-request)
|
||||||
|
(assoc :params (d/without-qualified data)))]
|
||||||
|
|
||||||
|
(-> params
|
||||||
|
(assoc :app.rpc/request-at (ct/now))
|
||||||
|
(with-meta {:app.http/request request}))))
|
||||||
|
|
||||||
(defn command!
|
(defn command!
|
||||||
[{:keys [::type] :as data}]
|
[{:keys [::type] :as data}]
|
||||||
(let [[mdata method-fn] (get-in *system* [:app.rpc/methods type])]
|
(let [[mdata method-fn] (get-in *system* [:app.rpc/methods type])]
|
||||||
@ -387,10 +465,8 @@
|
|||||||
:code :rpc-method-not-found
|
:code :rpc-method-not-found
|
||||||
:hint (str/ffmt "rpc method '%' not found" (name type))))
|
:hint (str/ffmt "rpc method '%' not found" (name type))))
|
||||||
|
|
||||||
;; (app.common.pprint/pprint (:app.rpc/methods *system*))
|
(let [params (prepare-rpc-params data)]
|
||||||
(try-on! (method-fn (-> data
|
(try-on! (method-fn params)))))
|
||||||
(dissoc ::type)
|
|
||||||
(assoc :app.rpc/request-at (ct/now)))))))
|
|
||||||
|
|
||||||
(defn management-command!
|
(defn management-command!
|
||||||
[{:keys [::type] :as data}]
|
[{:keys [::type] :as data}]
|
||||||
@ -399,9 +475,8 @@
|
|||||||
(ex/raise :type :assertion
|
(ex/raise :type :assertion
|
||||||
:code :rpc-method-not-found
|
:code :rpc-method-not-found
|
||||||
:hint (str/ffmt "management rpc method '%' not found" (name type))))
|
:hint (str/ffmt "management rpc method '%' not found" (name type))))
|
||||||
(try-on! (method-fn (-> data
|
(let [params (prepare-rpc-params data)]
|
||||||
(dissoc ::type)
|
(try-on! (method-fn params)))))
|
||||||
(assoc :app.rpc/request-at (ct/now)))))))
|
|
||||||
|
|
||||||
(defn run-task!
|
(defn run-task!
|
||||||
([name]
|
([name]
|
||||||
|
|||||||
@ -28,78 +28,17 @@
|
|||||||
(t/use-fixtures :once th/state-init)
|
(t/use-fixtures :once th/state-init)
|
||||||
(t/use-fixtures :each th/database-reset)
|
(t/use-fixtures :each th/database-reset)
|
||||||
|
|
||||||
(defrecord DummyRequest [headers cookies method body-stream
|
|
||||||
remote-addr server-name server-port
|
|
||||||
scheme protocol path query ssl-client-cert]
|
|
||||||
yreq/IRequestCookies
|
|
||||||
(get-cookie [_ name]
|
|
||||||
{:value (get cookies name)})
|
|
||||||
|
|
||||||
yreq/IRequest
|
|
||||||
(get-header [_ name]
|
|
||||||
(get headers name))
|
|
||||||
(method [_] method)
|
|
||||||
(body [_] body-stream)
|
|
||||||
(path [_] path)
|
|
||||||
(query [_] query)
|
|
||||||
(server-port [_] server-port)
|
|
||||||
(server-name [_] server-name)
|
|
||||||
(remote-addr [_] remote-addr)
|
|
||||||
(ssl-client-cert [_] ssl-client-cert)
|
|
||||||
(scheme [_] scheme)
|
|
||||||
(protocol [_] protocol))
|
|
||||||
|
|
||||||
(defn- make-dummy-request
|
|
||||||
"Constructs a DummyRequest from an options map. Every key is
|
|
||||||
optional; missing values fall back to sensible defaults. New
|
|
||||||
fields added to DummyRequest won't break existing call sites
|
|
||||||
as long as this constructor keeps its `:or` defaults in sync.
|
|
||||||
|
|
||||||
Recognized keys:
|
|
||||||
:headers — map of header name → value
|
|
||||||
:cookies — map of cookie name → value
|
|
||||||
:method — HTTP method keyword (default :get)
|
|
||||||
:body-stream — InputStream for the body (used directly)
|
|
||||||
:body-bytes — bytes or string for the body; wrapped in a
|
|
||||||
ByteArrayInputStream if :body-stream is not
|
|
||||||
given
|
|
||||||
:remote-addr — string (default \"127.0.0.1\")
|
|
||||||
:server-name — string (default \"test\")
|
|
||||||
:server-port — long (default 0)
|
|
||||||
:scheme — keyword (default :http)
|
|
||||||
:protocol — string (default \"HTTP/1.1\")
|
|
||||||
:path — string (default \"/test\")
|
|
||||||
:query — string or nil (default nil)
|
|
||||||
:ssl-client-cert — X509Certificate or nil (default nil)"
|
|
||||||
[{:keys [headers cookies method body-stream body-bytes
|
|
||||||
remote-addr server-name server-port scheme protocol
|
|
||||||
path query ssl-client-cert]
|
|
||||||
:or {headers {} cookies {} method :get
|
|
||||||
body-stream nil
|
|
||||||
remote-addr "127.0.0.1" server-name "test" server-port 0
|
|
||||||
scheme :http protocol "HTTP/1.1" path "/test" query nil
|
|
||||||
ssl-client-cert nil}}]
|
|
||||||
(let [body-stream (or body-stream
|
|
||||||
(when body-bytes
|
|
||||||
(java.io.ByteArrayInputStream.
|
|
||||||
(if (string? body-bytes)
|
|
||||||
(.getBytes ^String body-bytes "UTF-8")
|
|
||||||
body-bytes))))]
|
|
||||||
(->DummyRequest headers cookies method body-stream
|
|
||||||
remote-addr server-name server-port
|
|
||||||
scheme protocol path query ssl-client-cert)))
|
|
||||||
|
|
||||||
(t/deftest auth-middleware-1
|
(t/deftest auth-middleware-1
|
||||||
(let [request (volatile! nil)
|
(let [request (volatile! nil)
|
||||||
handler (#'app.http.middleware/wrap-auth
|
handler (#'app.http.middleware/wrap-auth
|
||||||
(fn [req] (vreset! request req))
|
(fn [req] (vreset! request req))
|
||||||
{})]
|
{})]
|
||||||
|
|
||||||
(handler (make-dummy-request {}))
|
(handler (th/make-dummy-request {}))
|
||||||
|
|
||||||
(t/is (nil? (::http/auth-data @request)))
|
(t/is (nil? (::http/auth-data @request)))
|
||||||
|
|
||||||
(handler (make-dummy-request {:headers {"authorization" "Token aaaa"}}))
|
(handler (th/make-dummy-request {:headers {"authorization" "Token aaaa"}}))
|
||||||
|
|
||||||
(let [{:keys [token claims] token-type :type} (get @request ::http/auth-data)]
|
(let [{:keys [token claims] token-type :type} (get @request ::http/auth-data)]
|
||||||
(t/is (= :token token-type))
|
(t/is (= :token token-type))
|
||||||
@ -112,10 +51,10 @@
|
|||||||
(fn [req] (vreset! request req))
|
(fn [req] (vreset! request req))
|
||||||
{})]
|
{})]
|
||||||
|
|
||||||
(handler (make-dummy-request {}))
|
(handler (th/make-dummy-request {}))
|
||||||
(t/is (nil? (::http/auth-data @request)))
|
(t/is (nil? (::http/auth-data @request)))
|
||||||
|
|
||||||
(handler (make-dummy-request {:headers {"authorization" "Bearer aaaa"}}))
|
(handler (th/make-dummy-request {:headers {"authorization" "Bearer aaaa"}}))
|
||||||
|
|
||||||
(let [{:keys [token claims] token-type :type} (get @request ::http/auth-data)]
|
(let [{:keys [token claims] token-type :type} (get @request ::http/auth-data)]
|
||||||
(t/is (= :bearer token-type))
|
(t/is (= :bearer token-type))
|
||||||
@ -128,10 +67,10 @@
|
|||||||
(fn [req] (vreset! request req))
|
(fn [req] (vreset! request req))
|
||||||
{})]
|
{})]
|
||||||
|
|
||||||
(handler (make-dummy-request {}))
|
(handler (th/make-dummy-request {}))
|
||||||
(t/is (nil? (::http/auth-data @request)))
|
(t/is (nil? (::http/auth-data @request)))
|
||||||
|
|
||||||
(handler (make-dummy-request {:cookies {"auth-token" "foobar"}}))
|
(handler (th/make-dummy-request {:cookies {"auth-token" "foobar"}}))
|
||||||
|
|
||||||
(let [{:keys [token claims] token-type :type} (get @request ::http/auth-data)]
|
(let [{:keys [token claims] token-type :type} (get @request ::http/auth-data)]
|
||||||
(t/is (= :cookie token-type))
|
(t/is (= :cookie token-type))
|
||||||
@ -143,16 +82,16 @@
|
|||||||
(fn [req] {::yres/status 200})
|
(fn [req] {::yres/status 200})
|
||||||
{:test1 "secret-key"})]
|
{:test1 "secret-key"})]
|
||||||
|
|
||||||
(let [response (handler (make-dummy-request {}))]
|
(let [response (handler (th/make-dummy-request {}))]
|
||||||
(t/is (= 403 (::yres/status response))))
|
(t/is (= 403 (::yres/status response))))
|
||||||
|
|
||||||
(let [response (handler (make-dummy-request {:headers {"x-shared-key" "secret-key2"}}))]
|
(let [response (handler (th/make-dummy-request {:headers {"x-shared-key" "secret-key2"}}))]
|
||||||
(t/is (= 403 (::yres/status response))))
|
(t/is (= 403 (::yres/status response))))
|
||||||
|
|
||||||
(let [response (handler (make-dummy-request {:headers {"x-shared-key" "secret-key"}}))]
|
(let [response (handler (th/make-dummy-request {:headers {"x-shared-key" "secret-key"}}))]
|
||||||
(t/is (= 403 (::yres/status response))))
|
(t/is (= 403 (::yres/status response))))
|
||||||
|
|
||||||
(let [response (handler (make-dummy-request {:headers {"x-shared-key" "test1 secret-key"}}))]
|
(let [response (handler (th/make-dummy-request {:headers {"x-shared-key" "test1 secret-key"}}))]
|
||||||
(t/is (= 200 (::yres/status response))))))
|
(t/is (= 200 (::yres/status response))))))
|
||||||
|
|
||||||
(t/deftest access-token-authz
|
(t/deftest access-token-authz
|
||||||
@ -263,7 +202,7 @@
|
|||||||
:user-agent "user agent"})
|
:user-agent "user agent"})
|
||||||
(#'session/assign-token cfg))
|
(#'session/assign-token cfg))
|
||||||
|
|
||||||
response (handler (make-dummy-request {:cookies {"auth-token" (:token session)}}))
|
response (handler (th/make-dummy-request {:cookies {"auth-token" (:token session)}}))
|
||||||
|
|
||||||
{:keys [token claims] token-type :type}
|
{:keys [token claims] token-type :type}
|
||||||
(get response ::http/auth-data)]
|
(get response ::http/auth-data)]
|
||||||
@ -290,7 +229,7 @@
|
|||||||
;; value with a backslash followed by '}', which
|
;; value with a backslash followed by '}', which
|
||||||
;; clojure.data.json v0.5.x cannot handle.
|
;; clojure.data.json v0.5.x cannot handle.
|
||||||
body (.getBytes "{\"x\": \"\\}\"}" "UTF-8")
|
body (.getBytes "{\"x\": \"\\}\"}" "UTF-8")
|
||||||
request (make-dummy-request
|
request (th/make-dummy-request
|
||||||
{:method :post
|
{:method :post
|
||||||
:headers {"content-type" "application/json"}
|
:headers {"content-type" "application/json"}
|
||||||
:body-bytes body})
|
:body-bytes body})
|
||||||
@ -309,7 +248,7 @@
|
|||||||
;; error.
|
;; error.
|
||||||
(let [handler (#'app.http.middleware/wrap-parse-request
|
(let [handler (#'app.http.middleware/wrap-parse-request
|
||||||
(fn [_] (throw (RequestTooBigException. "too large"))))
|
(fn [_] (throw (RequestTooBigException. "too large"))))
|
||||||
request (make-dummy-request
|
request (th/make-dummy-request
|
||||||
{:method :post
|
{:method :post
|
||||||
:headers {"content-type" "application/json"}
|
:headers {"content-type" "application/json"}
|
||||||
:body-bytes (.getBytes "{}" "UTF-8")})
|
:body-bytes (.getBytes "{}" "UTF-8")})
|
||||||
@ -327,7 +266,7 @@
|
|||||||
;; should convert it to a 400 :malformed-json validation error.
|
;; should convert it to a 400 :malformed-json validation error.
|
||||||
(let [handler (#'app.http.middleware/wrap-parse-request
|
(let [handler (#'app.http.middleware/wrap-parse-request
|
||||||
(fn [_] (throw (java.io.EOFException. "stream closed"))))
|
(fn [_] (throw (java.io.EOFException. "stream closed"))))
|
||||||
request (make-dummy-request
|
request (th/make-dummy-request
|
||||||
{:method :post
|
{:method :post
|
||||||
:headers {"content-type" "application/json"}
|
:headers {"content-type" "application/json"}
|
||||||
:body-bytes (.getBytes "{}" "UTF-8")})
|
:body-bytes (.getBytes "{}" "UTF-8")})
|
||||||
@ -350,7 +289,7 @@
|
|||||||
(.initCause iae))
|
(.initCause iae))
|
||||||
handler (#'app.http.middleware/wrap-parse-request
|
handler (#'app.http.middleware/wrap-parse-request
|
||||||
(fn [_] (throw wrapped)))
|
(fn [_] (throw wrapped)))
|
||||||
request (make-dummy-request
|
request (th/make-dummy-request
|
||||||
{:method :post
|
{:method :post
|
||||||
:headers {"content-type" "application/json"}
|
:headers {"content-type" "application/json"}
|
||||||
:body-bytes (.getBytes "{}" "UTF-8")})
|
:body-bytes (.getBytes "{}" "UTF-8")})
|
||||||
@ -368,7 +307,7 @@
|
|||||||
;; :unexpected. This is the "true internal error" path.
|
;; :unexpected. This is the "true internal error" path.
|
||||||
(let [handler (#'app.http.middleware/wrap-parse-request
|
(let [handler (#'app.http.middleware/wrap-parse-request
|
||||||
(fn [_] (throw (RuntimeException. "boom"))))
|
(fn [_] (throw (RuntimeException. "boom"))))
|
||||||
request (make-dummy-request
|
request (th/make-dummy-request
|
||||||
{:method :post
|
{:method :post
|
||||||
:headers {"content-type" "application/json"}
|
:headers {"content-type" "application/json"}
|
||||||
:body-bytes (.getBytes "{}" "UTF-8")})
|
:body-bytes (.getBytes "{}" "UTF-8")})
|
||||||
@ -388,7 +327,7 @@
|
|||||||
;; with :code :io-exception.
|
;; with :code :io-exception.
|
||||||
(let [handler (#'app.http.middleware/wrap-parse-request
|
(let [handler (#'app.http.middleware/wrap-parse-request
|
||||||
(fn [_] (throw (java.io.IOException. "network gone"))))
|
(fn [_] (throw (java.io.IOException. "network gone"))))
|
||||||
request (make-dummy-request
|
request (th/make-dummy-request
|
||||||
{:method :post
|
{:method :post
|
||||||
:headers {"content-type" "application/json"}
|
:headers {"content-type" "application/json"}
|
||||||
:body-bytes (.getBytes "{}" "UTF-8")})
|
:body-bytes (.getBytes "{}" "UTF-8")})
|
||||||
|
|||||||
@ -22,13 +22,21 @@
|
|||||||
(let [schema @#'binfile/schema:import-binfile
|
(let [schema @#'binfile/schema:import-binfile
|
||||||
validator (sm/lazy-validator schema)
|
validator (sm/lazy-validator schema)
|
||||||
|
|
||||||
valid-params {:name "test"
|
valid-params
|
||||||
:project-id (uuid/random)
|
{:name "test"
|
||||||
:version 3
|
:project-id (uuid/random)
|
||||||
:upload-id (uuid/random)}]
|
:version 3
|
||||||
|
:upload-id (uuid/random)}
|
||||||
|
|
||||||
|
params-with-file-id
|
||||||
|
(assoc valid-params :file-id (uuid/random))]
|
||||||
|
|
||||||
(t/is (true? (validator valid-params))
|
(t/is (true? (validator valid-params))
|
||||||
"params without file-id should be valid")
|
"params without file-id should be valid")
|
||||||
|
|
||||||
(t/is (not (contains? (sm/keys (second schema)) :file-id))
|
(t/is (not (contains? (sm/keys (second schema)) :file-id))
|
||||||
"file-id should not be a declared parameter")))
|
"file-id should not be a declared parameter")
|
||||||
|
|
||||||
|
;; Params with file-id should fail (schema closed)
|
||||||
|
(t/is (false? (validator params-with-file-id))
|
||||||
|
"params with file-id should be rejected")))
|
||||||
|
|||||||
@ -1107,8 +1107,11 @@
|
|||||||
body))
|
body))
|
||||||
|
|
||||||
(let [[_ method-fn] (get-in th/*system* [:app.rpc/methods :get-nitrate-activation-code-request])
|
(let [[_ method-fn] (get-in th/*system* [:app.rpc/methods :get-nitrate-activation-code-request])
|
||||||
result (method-fn {::rpc/profile-id (:id profile)
|
request (assoc (th/make-dummy-request) :params {})
|
||||||
::rpc/request-at now})
|
params (-> {::rpc/profile-id (:id profile)
|
||||||
|
::rpc/request-at now}
|
||||||
|
(with-meta {:app.http/request request}))
|
||||||
|
result (method-fn params)
|
||||||
headers (::http/headers (meta result))]
|
headers (::http/headers (meta result))]
|
||||||
(t/is (rph/wrapped? result))
|
(t/is (rph/wrapped? result))
|
||||||
(t/is (= "text/plain" (get headers "content-type")))
|
(t/is (= "text/plain" (get headers "content-type")))
|
||||||
|
|||||||
913
backend/test/backend_tests/rpc_test.clj
Normal file
913
backend/test/backend_tests/rpc_test.clj
Normal file
@ -0,0 +1,913 @@
|
|||||||
|
;; 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 Sucursal en España SL
|
||||||
|
|
||||||
|
(ns backend-tests.rpc-test
|
||||||
|
(:require
|
||||||
|
[app.common.exceptions :as ex]
|
||||||
|
[app.common.time :as ct]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
|
[app.http :as http]
|
||||||
|
[app.http.access-token :as actoken]
|
||||||
|
[app.http.session :as session]
|
||||||
|
[app.rpc :as rpc]
|
||||||
|
[app.rpc.cond :as cond]
|
||||||
|
[app.rpc.helpers :as rph]
|
||||||
|
[app.util.inet :as inet]
|
||||||
|
[backend-tests.helpers :as th]
|
||||||
|
[clojure.test :as t]
|
||||||
|
[yetti.response :as yres]))
|
||||||
|
|
||||||
|
(defn- echo-handler
|
||||||
|
"Handler that returns the data map it receives, for inspection."
|
||||||
|
[data]
|
||||||
|
data)
|
||||||
|
|
||||||
|
(defn- make-handler
|
||||||
|
"Creates an RPC handler with a single :test-method backed by echo-handler.
|
||||||
|
Extra methods are merged in; each value is [metadata handler-fn]."
|
||||||
|
([] (make-handler {}))
|
||||||
|
([extra-methods]
|
||||||
|
(let [methods (merge {:test-method [{} echo-handler]}
|
||||||
|
extra-methods)]
|
||||||
|
(rpc/make-rpc-handler methods))))
|
||||||
|
|
||||||
|
(defn- make-request
|
||||||
|
"Builds a dummy request. Extra qualified-keyword keys are merged onto it."
|
||||||
|
[& {:keys [method path-params headers extras]
|
||||||
|
:or {method :post
|
||||||
|
path-params {:method-name "test-method"}
|
||||||
|
headers {}
|
||||||
|
extras {}}}]
|
||||||
|
(-> (th/make-dummy-request {:method method
|
||||||
|
:headers headers})
|
||||||
|
(assoc :path-params path-params
|
||||||
|
:params {})
|
||||||
|
(merge extras)))
|
||||||
|
|
||||||
|
(defn- call
|
||||||
|
"Invokes handler with a built request, returns the full response map."
|
||||||
|
([handler opts]
|
||||||
|
(handler (make-request opts)))
|
||||||
|
([handler]
|
||||||
|
(call handler {})))
|
||||||
|
|
||||||
|
(defn- call-body
|
||||||
|
"Invokes handler with a built request, returns ::yres/body from response."
|
||||||
|
([handler opts]
|
||||||
|
(::yres/body (call handler opts)))
|
||||||
|
([handler]
|
||||||
|
(::yres/body (call handler))))
|
||||||
|
|
||||||
|
(defn- call-ex
|
||||||
|
"Invokes handler, catches and returns the exception."
|
||||||
|
([handler opts]
|
||||||
|
(try (call handler opts) (catch Exception e e)))
|
||||||
|
([handler]
|
||||||
|
(call-ex handler {})))
|
||||||
|
|
||||||
|
(def fixed-time (ct/now))
|
||||||
|
(def fixed-uuid (uuid/custom 11111111 22222222))
|
||||||
|
|
||||||
|
;; --- BASIC HANDLER DISPATCH
|
||||||
|
|
||||||
|
(t/deftest handler-receives-handler-name
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler)]
|
||||||
|
(t/is (= "test-method" (::rpc/handler-name data))))))
|
||||||
|
|
||||||
|
(t/deftest handler-receives-ip-addr
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "10.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler)]
|
||||||
|
(t/is (= "10.0.0.1" (::rpc/ip-addr data))))))
|
||||||
|
|
||||||
|
(t/deftest handler-receives-request-at
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler)]
|
||||||
|
(t/is (= fixed-time (::rpc/request-at data))))))
|
||||||
|
|
||||||
|
(t/deftest handler-receives-request-id
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler)]
|
||||||
|
(t/is (= fixed-uuid (::rpc/request-id data))))))
|
||||||
|
|
||||||
|
(t/deftest unknown-handler-name-raises-not-found
|
||||||
|
(let [handler (make-handler)
|
||||||
|
ex (call-ex handler {:path-params {:method-name "unknown"}})]
|
||||||
|
(t/is (some? ex))
|
||||||
|
(t/is (th/ex-of-type? ex :not-found))))
|
||||||
|
|
||||||
|
;; --- HANDLER-NAME EDGE CASES
|
||||||
|
|
||||||
|
(t/deftest nil-path-params-falls-to-default-handler
|
||||||
|
(let [handler (make-handler)
|
||||||
|
request (make-request {:path-params nil})
|
||||||
|
ex (try (handler request) (catch Exception e e))]
|
||||||
|
(t/is (some? ex))
|
||||||
|
(t/is (th/ex-of-type? ex :not-found))))
|
||||||
|
|
||||||
|
(t/deftest missing-method-name-falls-to-default-handler
|
||||||
|
(let [handler (make-handler)
|
||||||
|
request (make-request {:path-params {}})
|
||||||
|
ex (try (handler request) (catch Exception e e))]
|
||||||
|
(t/is (some? ex))
|
||||||
|
(t/is (th/ex-of-type? ex :not-found))))
|
||||||
|
|
||||||
|
(t/deftest empty-method-name-falls-to-default-handler
|
||||||
|
(let [handler (make-handler)
|
||||||
|
request (make-request {:path-params {:method-name ""}})
|
||||||
|
ex (try (handler request) (catch Exception e e))]
|
||||||
|
(t/is (some? ex))
|
||||||
|
(t/is (th/ex-of-type? ex :not-found))))
|
||||||
|
|
||||||
|
(t/deftest get-with-exact-get-prefix-no-suffix-allowed
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler {:get- [{} echo-handler]})
|
||||||
|
data (call-body handler
|
||||||
|
{:method :get
|
||||||
|
:path-params {:method-name "get-"}})]
|
||||||
|
(t/is (= "get-" (::rpc/handler-name data))))))
|
||||||
|
|
||||||
|
;; --- AUTH: SESSION
|
||||||
|
|
||||||
|
(t/deftest session-auth-profile-id
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [pid (uuid/next)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::session/profile-id pid}})]
|
||||||
|
(t/is (= pid (::rpc/profile-id data))))))
|
||||||
|
|
||||||
|
(t/deftest session-auth-type
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [pid (uuid/next)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::session/profile-id pid}})]
|
||||||
|
(t/is (= :session (::rpc/auth-type data))))))
|
||||||
|
|
||||||
|
(t/deftest session-auth-no-token-perms
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [pid (uuid/next)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::session/profile-id pid}})]
|
||||||
|
(t/is (not (contains? data ::rpc/token-perms))))))
|
||||||
|
|
||||||
|
(t/deftest nil-session-profile-id-not-used
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::session/profile-id nil}})]
|
||||||
|
(t/is (not (contains? data ::rpc/profile-id)))
|
||||||
|
(t/is (not (contains? data ::rpc/auth-type))))))
|
||||||
|
|
||||||
|
;; --- AUTH: TOKEN
|
||||||
|
|
||||||
|
(t/deftest token-auth-profile-id
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [pid (uuid/next)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::actoken/profile-id pid}})]
|
||||||
|
(t/is (= pid (::rpc/profile-id data))))))
|
||||||
|
|
||||||
|
(t/deftest token-auth-type
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [pid (uuid/next)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::actoken/profile-id pid}})]
|
||||||
|
(t/is (= :token (::rpc/auth-type data))))))
|
||||||
|
|
||||||
|
(t/deftest token-auth-with-perms
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [pid (uuid/next)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::actoken/profile-id pid
|
||||||
|
::actoken/perms #{"view" "edit"}}})]
|
||||||
|
(t/is (= #{"view" "edit"} (::rpc/token-perms data))))))
|
||||||
|
|
||||||
|
(t/deftest token-auth-perms-default-to-empty-set
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [pid (uuid/next)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::actoken/profile-id pid}})]
|
||||||
|
(t/is (= #{} (::rpc/token-perms data))))))
|
||||||
|
|
||||||
|
(t/deftest token-auth-perms-coerced-from-vector
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [pid (uuid/next)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::actoken/profile-id pid
|
||||||
|
::actoken/perms ["view" "edit"]}})]
|
||||||
|
(t/is (set? (::rpc/token-perms data)))
|
||||||
|
(t/is (= #{"view" "edit"} (::rpc/token-perms data))))))
|
||||||
|
|
||||||
|
(t/deftest token-auth-perms-coerced-from-list
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [pid (uuid/next)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::actoken/profile-id pid
|
||||||
|
::actoken/perms (list "view" "edit")}})]
|
||||||
|
(t/is (set? (::rpc/token-perms data)))
|
||||||
|
(t/is (= #{"view" "edit"} (::rpc/token-perms data))))))
|
||||||
|
|
||||||
|
(t/deftest token-auth-with-nil-perms-defaults-to-empty-set
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [pid (uuid/next)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::actoken/profile-id pid
|
||||||
|
::actoken/perms nil}})]
|
||||||
|
(t/is (= #{} (::rpc/token-perms data))))))
|
||||||
|
|
||||||
|
(t/deftest token-auth-with-empty-perms
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [pid (uuid/next)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::actoken/profile-id pid
|
||||||
|
::actoken/perms #{}}})]
|
||||||
|
(t/is (= #{} (::rpc/token-perms data))))))
|
||||||
|
|
||||||
|
(t/deftest nil-actoken-profile-id-not-used
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::actoken/profile-id nil}})]
|
||||||
|
(t/is (not (contains? data ::rpc/profile-id)))
|
||||||
|
(t/is (not (contains? data ::rpc/auth-type))))))
|
||||||
|
|
||||||
|
;; --- AUTH: SHARED KEY
|
||||||
|
|
||||||
|
(t/deftest key-auth-profile-id-is-zero
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::http/auth-key-id "some-key"}})]
|
||||||
|
(t/is (= uuid/zero (::rpc/profile-id data))))))
|
||||||
|
|
||||||
|
(t/deftest key-auth-key-id-in-data
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::http/auth-key-id "some-key"}})]
|
||||||
|
(t/is (= "some-key" (::rpc/auth-key-id data))))))
|
||||||
|
|
||||||
|
(t/deftest nil-auth-key-id-not-used
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::http/auth-key-id nil}})]
|
||||||
|
(t/is (not (contains? data ::rpc/profile-id)))
|
||||||
|
(t/is (not (contains? data ::rpc/auth-key-id))))))
|
||||||
|
|
||||||
|
;; --- AUTH: UNAUTHENTICATED
|
||||||
|
|
||||||
|
(t/deftest unauthenticated-no-profile-id
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler)]
|
||||||
|
(t/is (not (contains? data ::rpc/profile-id))))))
|
||||||
|
|
||||||
|
(t/deftest unauthenticated-no-auth-type
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler)]
|
||||||
|
(t/is (not (contains? data ::rpc/auth-type))))))
|
||||||
|
|
||||||
|
;; --- AUTH: PRIORITY
|
||||||
|
|
||||||
|
(t/deftest session-overrides-token
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [session-pid (uuid/custom 1 1)
|
||||||
|
token-pid (uuid/custom 2 2)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::session/profile-id session-pid
|
||||||
|
::actoken/profile-id token-pid}})]
|
||||||
|
(t/is (= session-pid (::rpc/profile-id data)))
|
||||||
|
(t/is (= :session (::rpc/auth-type data))))))
|
||||||
|
|
||||||
|
(t/deftest session-overrides-key
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [session-pid (uuid/custom 1 1)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::session/profile-id session-pid
|
||||||
|
::http/auth-key-id "key-id"}})]
|
||||||
|
(t/is (= session-pid (::rpc/profile-id data)))
|
||||||
|
(t/is (= :session (::rpc/auth-type data)))
|
||||||
|
(t/is (= "key-id" (::rpc/auth-key-id data))))))
|
||||||
|
|
||||||
|
(t/deftest token-overrides-key
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [token-pid (uuid/custom 2 2)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::actoken/profile-id token-pid
|
||||||
|
::http/auth-key-id "key-id"}})]
|
||||||
|
(t/is (= token-pid (::rpc/profile-id data)))
|
||||||
|
(t/is (= :token (::rpc/auth-type data)))
|
||||||
|
(t/is (= "key-id" (::rpc/auth-key-id data))))))
|
||||||
|
|
||||||
|
(t/deftest session-overrides-token-and-key
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [session-pid (uuid/custom 1 1)
|
||||||
|
token-pid (uuid/custom 2 2)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::session/profile-id session-pid
|
||||||
|
::actoken/profile-id token-pid
|
||||||
|
::http/auth-key-id "key-id"}})]
|
||||||
|
(t/is (= session-pid (::rpc/profile-id data)))
|
||||||
|
(t/is (= :session (::rpc/auth-type data))))))
|
||||||
|
|
||||||
|
;; --- REQUEST METADATA: SESSION-ID
|
||||||
|
|
||||||
|
(t/deftest session-id-parsed-from-header
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [sid (uuid/next)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:headers {"x-session-id" (str sid)}})]
|
||||||
|
(t/is (= sid (::rpc/session-id data))))))
|
||||||
|
|
||||||
|
(t/deftest session-id-nil-when-no-header
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler)]
|
||||||
|
(t/is (nil? (::rpc/session-id data))))))
|
||||||
|
|
||||||
|
(t/deftest session-id-nil-when-invalid-uuid
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:headers {"x-session-id" "not-a-uuid"}})]
|
||||||
|
(t/is (nil? (::rpc/session-id data))))))
|
||||||
|
|
||||||
|
(t/deftest session-id-nil-for-empty-string
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:headers {"x-session-id" ""}})]
|
||||||
|
(t/is (nil? (::rpc/session-id data))))))
|
||||||
|
|
||||||
|
(t/deftest session-id-parses-uppercase-uuid
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [sid (uuid/next)
|
||||||
|
handler (make-handler)
|
||||||
|
upper (clojure.string/upper-case (str sid))
|
||||||
|
data (call-body handler
|
||||||
|
{:headers {"x-session-id" upper}})]
|
||||||
|
(t/is (= sid (::rpc/session-id data))))))
|
||||||
|
|
||||||
|
;; --- REQUEST METADATA: ETAG / COND KEY
|
||||||
|
|
||||||
|
(t/deftest cond-key-from-if-none-match
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:headers {"if-none-match" "W/\"abc123\""}})]
|
||||||
|
(t/is (= "W/\"abc123\"" (::cond/key data))))))
|
||||||
|
|
||||||
|
(t/deftest cond-key-nil-when-no-header
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler)
|
||||||
|
data (call-body handler)]
|
||||||
|
(t/is (nil? (::cond/key data))))))
|
||||||
|
|
||||||
|
;; --- REQUEST METADATA: META
|
||||||
|
|
||||||
|
(t/deftest data-meta-contains-request
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [request (make-request {})
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler {})]
|
||||||
|
(t/is (= request (::http/request (meta data)))))))
|
||||||
|
|
||||||
|
;; --- DATA COMPLETENESS
|
||||||
|
|
||||||
|
(t/deftest data-contains-all-expected-keys
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [pid (uuid/next)
|
||||||
|
handler (make-handler)
|
||||||
|
data (call-body handler
|
||||||
|
{:extras {::session/profile-id pid
|
||||||
|
::http/auth-key-id "key"}})]
|
||||||
|
(t/is (contains? data ::rpc/handler-name))
|
||||||
|
(t/is (contains? data ::rpc/ip-addr))
|
||||||
|
(t/is (contains? data ::rpc/request-at))
|
||||||
|
(t/is (contains? data ::rpc/request-id))
|
||||||
|
(t/is (contains? data ::rpc/session-id))
|
||||||
|
(t/is (contains? data ::cond/key))
|
||||||
|
(t/is (contains? data ::rpc/profile-id))
|
||||||
|
(t/is (contains? data ::rpc/auth-type))
|
||||||
|
(t/is (contains? data ::rpc/auth-key-id)))))
|
||||||
|
|
||||||
|
;; --- GET/HEAD RESTRICTION
|
||||||
|
|
||||||
|
(t/deftest get-restriction-non-get-handler
|
||||||
|
(let [handler (make-handler)
|
||||||
|
ex (call-ex handler
|
||||||
|
{:method :get
|
||||||
|
:path-params {:method-name "foo"}})]
|
||||||
|
(t/is (some? ex))
|
||||||
|
(t/is (th/ex-of-type? ex :restriction))
|
||||||
|
(t/is (th/ex-of-code? ex :method-not-allowed))))
|
||||||
|
|
||||||
|
(t/deftest head-restriction-non-get-handler
|
||||||
|
(let [handler (make-handler)
|
||||||
|
ex (call-ex handler
|
||||||
|
{:method :head
|
||||||
|
:path-params {:method-name "foo"}})]
|
||||||
|
(t/is (some? ex))
|
||||||
|
(t/is (th/ex-of-type? ex :restriction))
|
||||||
|
(t/is (th/ex-of-code? ex :method-not-allowed))))
|
||||||
|
|
||||||
|
(t/deftest get-allowed-for-get-prefixed-handler
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler {:get-profile [{} echo-handler]})
|
||||||
|
data (call-body handler
|
||||||
|
{:method :get
|
||||||
|
:path-params {:method-name "get-profile"}})]
|
||||||
|
(t/is (= "get-profile" (::rpc/handler-name data))))))
|
||||||
|
|
||||||
|
(t/deftest head-allowed-for-get-prefixed-handler
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler {:get-files [{} echo-handler]})
|
||||||
|
data (call-body handler
|
||||||
|
{:method :head
|
||||||
|
:path-params {:method-name "get-files"}})]
|
||||||
|
(t/is (= "get-files" (::rpc/handler-name data))))))
|
||||||
|
|
||||||
|
(t/deftest post-always-allowed
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler {:any-name [{} echo-handler]})
|
||||||
|
data (call-body handler
|
||||||
|
{:method :post
|
||||||
|
:path-params {:method-name "any-name"}})]
|
||||||
|
(t/is (= "any-name" (::rpc/handler-name data))))))
|
||||||
|
|
||||||
|
(t/deftest put-always-allowed
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler {:any-name [{} echo-handler]})
|
||||||
|
data (call-body handler
|
||||||
|
{:method :put
|
||||||
|
:path-params {:method-name "any-name"}})]
|
||||||
|
(t/is (= "any-name" (::rpc/handler-name data))))))
|
||||||
|
|
||||||
|
(t/deftest delete-always-allowed
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler {:any-name [{} echo-handler]})
|
||||||
|
data (call-body handler
|
||||||
|
{:method :delete
|
||||||
|
:path-params {:method-name "any-name"}})]
|
||||||
|
(t/is (= "any-name" (::rpc/handler-name data))))))
|
||||||
|
|
||||||
|
(t/deftest patch-always-allowed
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [handler (make-handler {:any-name [{} echo-handler]})
|
||||||
|
data (call-body handler
|
||||||
|
{:method :patch
|
||||||
|
:path-params {:method-name "any-name"}})]
|
||||||
|
(t/is (= "any-name" (::rpc/handler-name data))))))
|
||||||
|
|
||||||
|
(t/deftest get-to-non-get-handler-restriction-checks-before-handler-execution
|
||||||
|
(let [called? (atom false)
|
||||||
|
handler (make-handler {:foo [{} (fn [data]
|
||||||
|
(reset! called? true)
|
||||||
|
data)]})
|
||||||
|
ex (call-ex handler
|
||||||
|
{:method :get
|
||||||
|
:path-params {:method-name "foo"}})]
|
||||||
|
(t/is (some? ex))
|
||||||
|
(t/is (false? @called?))))
|
||||||
|
|
||||||
|
;; --- RESPONSE: NIL -> 204
|
||||||
|
|
||||||
|
(t/deftest nil-response-returns-204
|
||||||
|
(let [handler (make-handler {:nil-method [{} (constantly nil)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "nil-method"}})]
|
||||||
|
(t/is (= 204 (::yres/status response)))))
|
||||||
|
|
||||||
|
(t/deftest nil-response-gets-stream-content-type
|
||||||
|
(let [handler (make-handler {:nil-method [{} (constantly nil)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "nil-method"}})]
|
||||||
|
(t/is (= "application/octet-stream"
|
||||||
|
(get (::yres/headers response) "content-type")))))
|
||||||
|
|
||||||
|
;; --- RESPONSE: MAP -> 200
|
||||||
|
|
||||||
|
(t/deftest map-response-returns-200
|
||||||
|
(let [handler (make-handler {:map-method [{} (constantly {:foo "bar"})]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "map-method"}})]
|
||||||
|
(t/is (= 200 (::yres/status response)))
|
||||||
|
(t/is (= {:foo "bar"} (::yres/body response)))))
|
||||||
|
|
||||||
|
;; --- RESPONSE: FALSY NON-NIL VALUES
|
||||||
|
|
||||||
|
(t/deftest false-response-returns-200
|
||||||
|
(let [handler (make-handler {:false-method [{} (constantly false)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "false-method"}})]
|
||||||
|
(t/is (= 200 (::yres/status response)))
|
||||||
|
(t/is (false? (::yres/body response)))))
|
||||||
|
|
||||||
|
(t/deftest zero-response-returns-200
|
||||||
|
(let [handler (make-handler {:zero-method [{} (constantly 0)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "zero-method"}})]
|
||||||
|
(t/is (= 200 (::yres/status response)))
|
||||||
|
(t/is (= 0 (::yres/body response)))))
|
||||||
|
|
||||||
|
(t/deftest empty-map-response-returns-200
|
||||||
|
(let [handler (make-handler {:empty-method [{} (constantly {})]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "empty-method"}})]
|
||||||
|
(t/is (= 200 (::yres/status response)))
|
||||||
|
(t/is (= {} (::yres/body response)))))
|
||||||
|
|
||||||
|
;; --- RESPONSE: CUSTOM STATUS
|
||||||
|
|
||||||
|
(t/deftest custom-status-from-metadata
|
||||||
|
(let [result (with-meta {:ok true} {::http/status 201})
|
||||||
|
handler (make-handler {:created [{} (constantly result)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "created"}})]
|
||||||
|
(t/is (= 201 (::yres/status response)))))
|
||||||
|
|
||||||
|
;; --- RESPONSE: CUSTOM HEADERS
|
||||||
|
|
||||||
|
(t/deftest custom-headers-from-metadata
|
||||||
|
(let [result (with-meta {:ok true} {::http/headers {"x-custom" "val"}})
|
||||||
|
handler (make-handler {:hdr-method [{} (constantly result)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "hdr-method"}})]
|
||||||
|
(t/is (= "val" (get (::yres/headers response) "x-custom")))))
|
||||||
|
|
||||||
|
;; --- RESPONSE: CUSTOM STATUS AND HEADERS TOGETHER
|
||||||
|
|
||||||
|
(t/deftest custom-status-and-headers-from-metadata
|
||||||
|
(let [result (with-meta {:ok true}
|
||||||
|
{::http/status 202
|
||||||
|
::http/headers {"x-request-id" "req-123"}})
|
||||||
|
handler (make-handler {:both [{} (constantly result)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "both"}})]
|
||||||
|
(t/is (= 202 (::yres/status response)))
|
||||||
|
(t/is (= "req-123" (get (::yres/headers response) "x-request-id")))))
|
||||||
|
|
||||||
|
;; --- RESPONSE: FUNCTION
|
||||||
|
|
||||||
|
(t/deftest function-response-called-with-request
|
||||||
|
(let [captured (atom nil)
|
||||||
|
resp-fn (fn [req]
|
||||||
|
(reset! captured req)
|
||||||
|
{::yres/status 200
|
||||||
|
::yres/body "from-fn"})
|
||||||
|
handler (make-handler {:fn-method [{} (constantly resp-fn)]})
|
||||||
|
request (make-request {:path-params {:method-name "fn-method"}})
|
||||||
|
response (handler request)]
|
||||||
|
(t/is (= request @captured))
|
||||||
|
(t/is (= 200 (::yres/status response)))
|
||||||
|
(t/is (= "from-fn" (::yres/body response)))))
|
||||||
|
|
||||||
|
(t/deftest function-response-returning-nil
|
||||||
|
(let [handler (make-handler {:fn-nil [{} (constantly (fn [_] nil))]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "fn-nil"}})]
|
||||||
|
(t/is (nil? (::yres/body response)))))
|
||||||
|
|
||||||
|
(t/deftest function-response-uses-return-value-as-is
|
||||||
|
(let [resp-fn (fn [_req]
|
||||||
|
{:result "ok"})
|
||||||
|
handler (make-handler {:fn-meta [{} (constantly resp-fn)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "fn-meta"}})]
|
||||||
|
;; When handler returns a fn, handle-response calls (fn request)
|
||||||
|
;; and uses the return value directly as the response.
|
||||||
|
(t/is (= {:result "ok"} response))))
|
||||||
|
|
||||||
|
;; --- RESPONSE: METADATA WRAPPER
|
||||||
|
|
||||||
|
(t/deftest metadata-wrapper-unwrapped-in-response
|
||||||
|
(let [wrapped (rph/wrap {:data "value"})
|
||||||
|
handler (make-handler {:wrap-method [{} (constantly wrapped)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "wrap-method"}})]
|
||||||
|
(t/is (= {:data "value"} (::yres/body response)))))
|
||||||
|
|
||||||
|
(t/deftest metadata-wrapper-preserves-metadata
|
||||||
|
(let [wrapped (-> (rph/wrap {:data "value"})
|
||||||
|
(vary-meta assoc ::http/status 201))
|
||||||
|
handler (make-handler {:wrap-meta [{} (constantly wrapped)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "wrap-meta"}})]
|
||||||
|
(t/is (= 201 (::yres/status response)))))
|
||||||
|
|
||||||
|
(t/deftest metadata-wrapper-with-custom-headers
|
||||||
|
(let [wrapped (-> (rph/wrap {:data "value"})
|
||||||
|
(vary-meta assoc ::http/headers {"x-wrap" "yes"}))
|
||||||
|
handler (make-handler {:wrap-hdr [{} (constantly wrapped)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "wrap-hdr"}})]
|
||||||
|
(t/is (= "yes" (get (::yres/headers response) "x-wrap")))))
|
||||||
|
|
||||||
|
;; --- RESPONSE: STREAM BODY
|
||||||
|
|
||||||
|
(t/deftest stream-response-default-content-type
|
||||||
|
(let [stream (yres/stream-body (fn [_response _output] nil))
|
||||||
|
handler (make-handler {:stream-method [{} (constantly stream)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "stream-method"}})]
|
||||||
|
(t/is (= "application/octet-stream"
|
||||||
|
(get (::yres/headers response) "content-type")))))
|
||||||
|
|
||||||
|
(t/deftest stream-response-preserves-existing-content-type
|
||||||
|
(let [stream (with-meta
|
||||||
|
(yres/stream-body (fn [_response _output] nil))
|
||||||
|
{::http/headers {"content-type" "text/plain"}})
|
||||||
|
handler (make-handler {:stream-ct [{} (constantly stream)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "stream-ct"}})]
|
||||||
|
(t/is (= "text/plain"
|
||||||
|
(get (::yres/headers response) "content-type")))))
|
||||||
|
|
||||||
|
;; --- RESPONSE TRANSFORMATION
|
||||||
|
|
||||||
|
(t/deftest response-transform-fns-applied
|
||||||
|
(let [transform (fn [_req resp]
|
||||||
|
(assoc-in resp [::yres/headers "x-transformed"] "yes"))
|
||||||
|
result (with-meta {:ok true}
|
||||||
|
{::rpc/response-transform-fns [transform]})
|
||||||
|
handler (make-handler {:transform [{} (constantly result)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "transform"}})]
|
||||||
|
(t/is (= "yes" (get (::yres/headers response) "x-transformed")))))
|
||||||
|
|
||||||
|
(t/deftest response-transform-fns-applied-in-order
|
||||||
|
(let [t1 (fn [_req resp]
|
||||||
|
(assoc-in resp [::yres/headers "x-step"] "1"))
|
||||||
|
t2 (fn [_req resp]
|
||||||
|
(update-in resp [::yres/headers "x-step"] str "-2"))
|
||||||
|
result (with-meta {:ok true}
|
||||||
|
{::rpc/response-transform-fns [t1 t2]})
|
||||||
|
handler (make-handler {:multi-transform [{} (constantly result)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "multi-transform"}})]
|
||||||
|
(t/is (= "1-2" (get (::yres/headers response) "x-step")))))
|
||||||
|
|
||||||
|
(t/deftest transform-fn-can-modify-status
|
||||||
|
(let [transform (fn [_req _resp]
|
||||||
|
{::yres/status 418
|
||||||
|
::yres/body "teapot"
|
||||||
|
::yres/headers {}})
|
||||||
|
result (with-meta {:ok true}
|
||||||
|
{::rpc/response-transform-fns [transform]})
|
||||||
|
handler (make-handler {:teapot [{} (constantly result)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "teapot"}})]
|
||||||
|
(t/is (= 418 (::yres/status response)))))
|
||||||
|
|
||||||
|
(t/deftest empty-transform-fns-no-op
|
||||||
|
(let [result (with-meta {:ok true}
|
||||||
|
{::rpc/response-transform-fns []})
|
||||||
|
handler (make-handler {:empty-t [{} (constantly result)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "empty-t"}})]
|
||||||
|
(t/is (= 200 (::yres/status response)))))
|
||||||
|
|
||||||
|
(t/deftest transform-receives-request-and-response
|
||||||
|
(let [captured-req (atom nil)
|
||||||
|
captured-resp (atom nil)
|
||||||
|
transform (fn [req resp]
|
||||||
|
(reset! captured-req req)
|
||||||
|
(reset! captured-resp resp)
|
||||||
|
resp)
|
||||||
|
result (with-meta {:ok true}
|
||||||
|
{::rpc/response-transform-fns [transform]})
|
||||||
|
handler (make-handler {:cap-t [{} (constantly result)]})
|
||||||
|
request (make-request {:path-params {:method-name "cap-t"}})
|
||||||
|
response (handler request)]
|
||||||
|
(t/is (= request @captured-req))
|
||||||
|
(t/is (some? @captured-resp))
|
||||||
|
(t/is (= 200 (::yres/status @captured-resp)))))
|
||||||
|
|
||||||
|
;; --- BEFORE-COMPLETE HOOKS
|
||||||
|
|
||||||
|
(t/deftest before-complete-hooks-called
|
||||||
|
(let [called? (atom false)
|
||||||
|
hook (fn [] (reset! called? true))
|
||||||
|
result (with-meta {:ok true}
|
||||||
|
{::rpc/before-complete-fns [hook]})
|
||||||
|
handler (make-handler {:hook-method [{} (constantly result)]})]
|
||||||
|
(call handler {:path-params {:method-name "hook-method"}})
|
||||||
|
(t/is (true? @called?))))
|
||||||
|
|
||||||
|
(t/deftest multiple-hooks-all-called
|
||||||
|
(let [called-a (atom false)
|
||||||
|
called-b (atom false)
|
||||||
|
hook-a (fn [] (reset! called-a true))
|
||||||
|
hook-b (fn [] (reset! called-b true))
|
||||||
|
result (with-meta {:ok true}
|
||||||
|
{::rpc/before-complete-fns [hook-a hook-b]})
|
||||||
|
handler (make-handler {:multi-hook [{} (constantly result)]})]
|
||||||
|
(call handler {:path-params {:method-name "multi-hook"}})
|
||||||
|
(t/is (true? @called-a))
|
||||||
|
(t/is (true? @called-b))))
|
||||||
|
|
||||||
|
(t/deftest before-complete-hook-errors-ignored
|
||||||
|
(let [hook (fn [] (throw (Exception. "hook error")))
|
||||||
|
result (with-meta {:ok true}
|
||||||
|
{::rpc/before-complete-fns [hook]})
|
||||||
|
handler (make-handler {:hook-err [{} (constantly result)]})]
|
||||||
|
(let [response (call handler
|
||||||
|
{:path-params {:method-name "hook-err"}})]
|
||||||
|
(t/is (= 200 (::yres/status response))))))
|
||||||
|
|
||||||
|
(t/deftest hook-error-does-not-prevent-other-hooks
|
||||||
|
(let [called? (atom false)
|
||||||
|
hook-a (fn [] (throw (Exception. "boom")))
|
||||||
|
hook-b (fn [] (reset! called? true))
|
||||||
|
result (with-meta {:ok true}
|
||||||
|
{::rpc/before-complete-fns [hook-a hook-b]})
|
||||||
|
handler (make-handler {:hook-chain [{} (constantly result)]})]
|
||||||
|
(call handler {:path-params {:method-name "hook-chain"}})
|
||||||
|
(t/is (true? @called?))))
|
||||||
|
|
||||||
|
(t/deftest empty-hooks-no-op
|
||||||
|
(let [result (with-meta {:ok true}
|
||||||
|
{::rpc/before-complete-fns []})
|
||||||
|
handler (make-handler {:empty-hooks [{} (constantly result)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "empty-hooks"}})]
|
||||||
|
(t/is (= 200 (::yres/status response)))))
|
||||||
|
|
||||||
|
;; --- METHODS MAP FORMAT
|
||||||
|
|
||||||
|
(t/deftest methods-map-uses-peek-to-extract-handler
|
||||||
|
(with-redefs [ct/now (constantly fixed-time)
|
||||||
|
uuid/next (constantly fixed-uuid)
|
||||||
|
inet/parse-request (constantly "127.0.0.1")]
|
||||||
|
(let [metadata {:some-meta "value"}
|
||||||
|
methods {:peek-test [metadata echo-handler]}
|
||||||
|
handler (rpc/make-rpc-handler methods)
|
||||||
|
data (call-body handler
|
||||||
|
{:path-params {:method-name "peek-test"}})]
|
||||||
|
(t/is (= "peek-test" (::rpc/handler-name data))))))
|
||||||
|
|
||||||
|
;; --- COND/*ENABLED* BINDING
|
||||||
|
|
||||||
|
(t/deftest cond-enabled-bound-to-true-inside-handler
|
||||||
|
(let [captured (atom nil)
|
||||||
|
handler (make-handler
|
||||||
|
{:cond-test [{} (fn [data]
|
||||||
|
(reset! captured cond/*enabled*)
|
||||||
|
data)]})]
|
||||||
|
(call handler {:path-params {:method-name "cond-test"}})
|
||||||
|
(t/is (true? @captured))))
|
||||||
|
|
||||||
|
(t/deftest cond-enabled-false-outside-handler
|
||||||
|
(let [handler (make-handler)]
|
||||||
|
(call handler)
|
||||||
|
(t/is (false? cond/*enabled*))))
|
||||||
|
|
||||||
|
(t/deftest cond-enabled-restored-after-handler-throws
|
||||||
|
(let [handler (make-handler
|
||||||
|
{:throwing [{} (fn [_]
|
||||||
|
(ex/raise :type :test :code :x))]})]
|
||||||
|
(try (call handler {:path-params {:method-name "throwing"}})
|
||||||
|
(catch Exception _))
|
||||||
|
(t/is (false? cond/*enabled*))))
|
||||||
|
|
||||||
|
;; --- HANDLER EXCEPTION PROPAGATION
|
||||||
|
|
||||||
|
(t/deftest handler-exception-propagates-with-type-and-code
|
||||||
|
(let [handler (make-handler
|
||||||
|
{:fail [{} (fn [_]
|
||||||
|
(ex/raise :type :test
|
||||||
|
:code :boom))]})
|
||||||
|
ex (call-ex handler {:path-params {:method-name "fail"}})]
|
||||||
|
(t/is (some? ex))
|
||||||
|
(t/is (th/ex-of-type? ex :test))
|
||||||
|
(t/is (th/ex-of-code? ex :boom))))
|
||||||
|
|
||||||
|
;; --- DEFAULT HANDLER
|
||||||
|
|
||||||
|
(t/deftest default-handler-for-missing-method
|
||||||
|
(let [handler (rpc/make-rpc-handler {})
|
||||||
|
ex (try (handler (make-request
|
||||||
|
{:path-params {:method-name "nonexistent"}}))
|
||||||
|
(catch Exception e e))]
|
||||||
|
(t/is (some? ex))
|
||||||
|
(t/is (th/ex-of-type? ex :not-found))))
|
||||||
|
|
||||||
|
;; --- RESPONSE: NIL BODY WITH CUSTOM HEADERS
|
||||||
|
|
||||||
|
(t/deftest nil-body-with-custom-headers
|
||||||
|
(let [wrapped (-> (rph/wrap nil)
|
||||||
|
(vary-meta assoc ::http/headers {"x-custom" "val"}))
|
||||||
|
handler (make-handler {:nil-hdr [{} (constantly wrapped)]})
|
||||||
|
response (call handler
|
||||||
|
{:path-params {:method-name "nil-hdr"}})]
|
||||||
|
(t/is (= 204 (::yres/status response)))
|
||||||
|
(t/is (= "val" (get (::yres/headers response) "x-custom")))))
|
||||||
Loading…
x
Reference in New Issue
Block a user