Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions src/ring/mock/request.clj
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
[clojure.string :as string]
[ring.util.codec :as codec]
[ring.util.mime-type :as mime])
(:import [java.io ByteArrayInputStream ByteArrayOutputStream File]
(:import [java.io ByteArrayInputStream ByteArrayOutputStream File InputStream]
[java.net URI]
[java.nio.charset Charset]
[java.util Map]
[org.apache.hc.core5.http ContentType HttpEntity]
[org.apache.hc.client5.http.entity.mime MultipartEntityBuilder]))

Expand Down Expand Up @@ -75,9 +77,9 @@
(defmethod body (class (byte-array 0)) [request bytes]
(-> request
(content-length (count bytes))
(assoc :body (java.io.ByteArrayInputStream. bytes))))
(assoc :body (ByteArrayInputStream. bytes))))

(defmethod body java.util.Map [request params]
(defmethod body Map [request params]
(-> request
(content-type "application/x-www-form-urlencoded")
(body (encode-params params))))
Expand All @@ -99,11 +101,30 @@
(defn- file? [f]
(instance? File f))

(defn- add-multipart-part [builder k v]
(defn- str->bytes ^bytes [^String s]
(.getBytes s ^Charset default-charset))

(defn- add-binary-body
[^MultipartEntityBuilder builder key value ^ContentType mimetype
^String filename]
(let [k (name key)]
(cond
(string? value)
(.addBinaryBody builder k (str->bytes value) mimetype filename)
(bytes? value)
(.addBinaryBody builder k ^bytes value mimetype filename)
(file? value)
(.addBinaryBody builder k ^File value mimetype filename)
(instance? InputStream value)
(.addBinaryBody builder k ^InputStream value mimetype filename)
:else
(throw (IllegalArgumentException.
(str "Cannot encode a value of type " (type value)
" as a multipart body."))))))

(defn- add-multipart-part [^MultipartEntityBuilder builder k v]
(let [param (if (map? v) v {:value v})
value (if (string? (:value param))
(.getBytes (:value param) default-charset)
(:value param))
value (if (map? v) (:value v) v)
mimetype (ContentType/parse
(or (:content-type param)
(when (file? value)
Expand All @@ -113,11 +134,11 @@
"application/octet-stream")))
filename (or (:filename param)
(when (file? value) (.getName ^File value)))]
(.addBinaryBody builder (name k) value mimetype filename)))
(add-binary-body builder (name k) value mimetype filename)))

(defn- multipart-entity ^HttpEntity [params]
(let [builder (MultipartEntityBuilder/create)]
(.setCharset builder default-charset)
(.setCharset builder ^Charset default-charset)
(doseq [[k v] params]
(add-multipart-part builder k v))
(.build builder)))
Expand Down Expand Up @@ -157,7 +178,7 @@
([method uri]
(request method uri nil))
([method uri params]
(let [uri (java.net.URI. uri)
(let [uri (URI. uri)
scheme (keyword (or (.getScheme uri) "http"))
host (or (.getHost uri) "localhost")
port (when (not= (.getPort uri) -1) (.getPort uri))
Expand Down
23 changes: 17 additions & 6 deletions test/ring/mock/request_test.clj
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
(ns ring.mock.request-test
(:require [clojure.java.io :as io]
[clojure.spec.alpha :as s]
[clojure.test :refer [deftest is testing]]
[ring.mock.request :refer [body content-length content-type cookie
header json-body multipart-body
query-string request]]))
query-string request]])
(:import [java.io InputStream]))

(deftest test-request
(testing "relative uri"
Expand Down Expand Up @@ -130,27 +132,27 @@
(deftest test-body
(testing "string body"
(let [resp (body {} "Hello World")]
(is (instance? java.io.InputStream (:body resp)))
(is (instance? InputStream (:body resp)))
(is (= (slurp (:body resp)) "Hello World"))
(is (= (:content-length resp) 11))))
(testing "map body"
(let [resp (body {} (array-map :foo "bar" :fi ["fi" "fo" "fum"]))]
(is (instance? java.io.InputStream (:body resp)))
(is (instance? InputStream (:body resp)))
(is (= (slurp (:body resp)) "foo=bar&fi=fi&fi=fo&fi=fum"))
(is (= (:content-length resp) 26))
(is (= (:content-type resp)
"application/x-www-form-urlencoded"))))
(testing "bytes body"
(let [resp (body {} (.getBytes "foo"))]
(is (instance? java.io.InputStream (:body resp)))
(is (instance? InputStream (:body resp)))
(is (= (slurp (:body resp)) "foo"))
(is (= (:content-length resp) 3)))))

(deftest test-json-body
(testing "json body"
(let [resp (json-body {} {:baz ["qu" "qi" "qo"]})]
(is (= (:content-type resp) "application/json"))
(is (instance? java.io.InputStream (:body resp)))
(is (instance? InputStream (:body resp)))
(is (= (slurp (:body resp))
"{\"baz\":[\"qu\",\"qi\",\"qo\"]}"))
(is (= (:content-length resp) 24)))))
Expand Down Expand Up @@ -229,7 +231,16 @@
"filename=\"test.html\"\r\n"
"Content-Type: text/html\r\n\r\n"
"a\n\r\n"
"--" boundary "--\r\n"))))))
"--" boundary "--\r\n")))))
(testing "not supported type"
(try
(multipart-body {} {:foo :keyword
:bar {:value :keyword
:content-type "text/html"
:filename "test.html"}})
(catch Exception e
(let [expected-message "Cannot encode a value of type class clojure.lang.Keyword as a multipart body."]
(is (= expected-message (.getMessage ^Exception e))))))))

(defmacro when-clojure-spec
[& body]
Expand Down