babashka/test/babashka/java_net_http_test.clj
Michiel Borkent ce6d4aaf34 Fix test
2021-10-08 14:43:57 +02:00

530 lines
22 KiB
Clojure

(ns babashka.java-net-http-test
(:require
[babashka.test-utils :as test-utils]
[clojure.edn :as edn]
[clojure.string :as str]
[clojure.test :as test :refer [deftest is]]
[org.httpkit.server :as httpkit.server]))
(defn bb [& exprs]
(edn/read-string (apply test-utils/bb nil (map str exprs))))
;; HttpClient
(deftest interop-test
(is (= :hello
(bb "-e"
(binding [*print-meta* true]
'(do
(def res (atom nil))
(def x (reify java.net.http.WebSocket$Listener (onOpen [this ws] (reset! res :hello))))
(.onOpen ^java.net.http.WebSocket$Listener x nil) @res))))))
(deftest send-test
(is (= [200 true]
(bb
'(do (ns net
(:import
(java.net URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)))
(def req
(-> (HttpRequest/newBuilder (URI. "https://www.clojure.org"))
(.GET)
(.build)))
(def client (HttpClient/newHttpClient))
(def res (.send client req (HttpResponse$BodyHandlers/ofString)))
[(.statusCode res) (string? (.body res))])))))
(deftest send-async-test
(is (= [200 true]
(bb
'(do
(ns net
(:import
(java.net URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)
(java.util.function Function)))
(let [client (HttpClient/newHttpClient)
req (-> (HttpRequest/newBuilder (URI. "https://www.clojure.org"))
(.GET)
(.build))]
(-> (.sendAsync client req (HttpResponse$BodyHandlers/ofString))
(.thenApply (reify Function (apply [_ res] [(.statusCode res) (string? (.body res))])))
(deref))))))))
;; HttpClient options
(deftest authenticator-test
(is (= [401 200]
(bb
'(do
(ns net
(:import
(java.net Authenticator
PasswordAuthentication
URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)))
(let [no-auth-client (HttpClient/newHttpClient)
req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/basic-auth"))
(.build))
handler (HttpResponse$BodyHandlers/discarding)
no-auth-res (.send no-auth-client req handler)
authenticator (proxy [Authenticator] []
(getPasswordAuthentication []
(PasswordAuthentication. "postman" (char-array "password"))))
auth-client (-> (HttpClient/newBuilder)
(.authenticator authenticator)
(.build))
auth-res (.send auth-client req handler)]
[(.statusCode no-auth-res) (.statusCode auth-res)]))))))
(deftest cookie-test
(is (= []
(bb '(do (ns net
(:import [java.net CookieManager]))
(-> (CookieManager.)
(.getCookieStore)
(.getCookies))))))
(is (= "www.postman-echo.com"
(bb '(do
(ns net
(:import
(java.net CookieManager
URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)))
(let [client (-> (HttpClient/newBuilder)
(.cookieHandler (CookieManager.))
(.build))
req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/get"))
(.GET)
(.build))]
(.send client req (HttpResponse$BodyHandlers/discarding))
(-> client
(.cookieHandler)
(.get)
(.getCookieStore)
(.getCookies)
first
(.getDomain))))))))
(deftest connect-timeout-test
(is (str/includes?
(bb
'(do
(ns net
(:import
(java.net URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)
(java.time Duration)))
(let [client (-> (HttpClient/newBuilder)
(.connectTimeout (Duration/ofMillis 1))
(.build))
req (-> (HttpRequest/newBuilder (URI. "Https://www.postman-echo.com/get"))
(.GET)
(.build))]
(try
(.send client req (HttpResponse$BodyHandlers/discarding))
(catch Throwable t
(-> (Throwable->map t)
:via
first
:type
name))))))
;; can be either java.net.http.HttpConnectTimeoutException or java.net.http.HttpTimeoutException
"TimeoutException")))
(deftest executor-test
(is (= 200
(bb
'(do
(ns net
(:import
(java.net URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)
(java.util.concurrent Executors)))
(let [uri (URI. "https://www.postman-echo.com/get")
req (-> (HttpRequest/newBuilder uri)
(.GET)
(.build))
client (-> (HttpClient/newBuilder)
(.executor (Executors/newSingleThreadExecutor))
(.build))
res (.send client req (HttpResponse$BodyHandlers/discarding))]
(.statusCode res)))))))
(deftest proxy-test
(is (= true
(bb
'(do
(ns net
(:import
(java.net ProxySelector)
(java.net.http HttpClient)))
(let [bespoke-proxy (proxy [ProxySelector] []
(connectFailed [_ _ _])
(select [_ _]))
client (-> (HttpClient/newBuilder)
(.proxy bespoke-proxy)
(.build))]
(= bespoke-proxy (-> (.proxy client)
(.get))))))))
(is (= 200
(bb
'(do
(ns net
(:import
(java.net ProxySelector
URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)))
(let [uri (URI. "https://www.postman-echo.com/get")
req (-> (HttpRequest/newBuilder uri)
(.build))
client (-> (HttpClient/newBuilder)
(.proxy (ProxySelector/getDefault))
(.build))
res (.send client req (HttpResponse$BodyHandlers/discarding))]
(.statusCode res)))))))
(deftest redirect-test
(let [redirect-prog
(fn [redirect-kind]
(str/replace (str '(do
(ns net
(:import
(java.net.http HttpClient
HttpClient$Redirect
HttpRequest
HttpRequest$BodyPublishers
HttpResponse$BodyHandlers)
(java.net URI)))
(defn log [x] (.println System/err x))
(let [req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com"))
(.GET)
(.timeout (java.time.Duration/ofSeconds 5))
(.build))
client (-> (HttpClient/newBuilder)
(.followRedirects :redirect/kind)
(.build))
handler (HttpResponse$BodyHandlers/discarding)]
(.statusCode (.send client req handler)))))
":redirect/kind"
(case redirect-kind
:never
"HttpClient$Redirect/NEVER"
:always
"HttpClient$Redirect/ALWAYS")))]
(println "Testing redirect always")
(is (= 200 (bb (redirect-prog :always))))
(println "Testing redirect never")
(is (= 302 (bb (redirect-prog :never))))))
(deftest ssl-context-test
(is (= {:expired "java.security.cert.CertificateExpiredException"
:revoked "java.security.cert.CertificateExpiredException"
:self-signed "sun.security.provider.certpath.SunCertPathBuilderException"
:untrusted-root "sun.security.provider.certpath.SunCertPathBuilderException"
:wrong-host "sun.security.provider.certpath.SunCertPathBuilderException"}
(bb
'(do
(ns net
(:import
(java.net URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)))
(defn send-and-catch [client req handler]
(try
(let [res (.send client req (HttpResponse$BodyHandlers/discarding))]
(.statusCode res))
(catch Throwable t
(-> (Throwable->map t) :via last :type name))))
(let [client (HttpClient/newHttpClient)
handler (HttpResponse$BodyHandlers/discarding)
reqs (->> [:expired
:self-signed
:revoked
:untrusted-root
:wrong-host]
(map (fn [k]
(let [req (-> (URI. (format "https://%s.badssl.com" (name k)))
(HttpRequest/newBuilder)
(.GET)
(.build))]
[k req])))
(into {}))]
(->> reqs
(map (fn [[k req]]
[k (send-and-catch client req handler)]))
(into {})))))))
(is (= {:expired 200
:self-signed 200
:untrusted-root 200}
(bb
'(do
(ns net
(:import
(java.net URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)
(java.security SecureRandom)
(java.security.cert X509Certificate)
(javax.net.ssl SSLContext
TrustManager
X509TrustManager)))
(let [insecure-trust-manager (reify X509TrustManager
(checkClientTrusted [_ _ _])
(checkServerTrusted [_ _ _])
(getAcceptedIssuers [_] (into-array X509Certificate [])))
insecure-trust-managers (into-array TrustManager [insecure-trust-manager])
insecure-context (doto (SSLContext/getInstance "TLS")
(.init nil
insecure-trust-managers
(SecureRandom.)))
client (-> (HttpClient/newBuilder)
(.sslContext insecure-context)
(.build))
handler (HttpResponse$BodyHandlers/discarding)
reqs (->> [:expired
:self-signed
:untrusted-root]
(map (fn [k]
(let [req (-> (URI. (format "https://%s.badssl.com" (name k)))
(HttpRequest/newBuilder)
(.GET)
(.build))]
[k req])))
(into {}))]
(->> reqs
(map (fn [[k req]]
[k (-> (.send client req handler)
(.statusCode))]))
(into {}))))))))
;; HttpRequest
(deftest body-publishers-test
(is (= true
(bb
'(do
(ns net
(:require
[cheshire.core :as json]
[clojure.java.io :as io]
[clojure.string :as str])
(:import
(java.net URI)
(java.net.http HttpClient
HttpRequest
HttpRequest$BodyPublishers
HttpResponse$BodyHandlers)
(java.util.function Supplier)))
(let [bp (HttpRequest$BodyPublishers/ofFile (.toPath (io/file "README.md")))
req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/post"))
(.method "POST" bp)
(.build))
client (HttpClient/newHttpClient)
res (.send client req (HttpResponse$BodyHandlers/ofString))
body-data (-> (.body res) (json/parse-string true) :data)]
(str/includes? body-data "babashka"))))))
(let [body "with love from java.net.http"]
(is (= {:of-input-stream body
:of-byte-array body
:of-byte-arrays body}
(bb
'(do
(ns net
(:require
[cheshire.core :as json]
[clojure.java.io :as io])
(:import
(java.net URI)
(java.net.http HttpClient
HttpRequest
HttpRequest$BodyPublishers
HttpResponse$BodyHandlers)
(java.util.function Supplier)))
(let [body "with love from java.net.http"
publishers {:of-input-stream (HttpRequest$BodyPublishers/ofInputStream
(reify Supplier (get [_] (io/input-stream (.getBytes body)))))
:of-byte-array (HttpRequest$BodyPublishers/ofByteArray (.getBytes body))
:of-byte-arrays (HttpRequest$BodyPublishers/ofByteArrays [(.getBytes body)])}
client (-> (HttpClient/newBuilder)
(.build))
body-data (fn [res] (-> (.body res) (json/parse-string true) :data))]
(->> publishers
(map (fn [[k body-publisher]]
(let [req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/post"))
(.method "POST" body-publisher)
(.build))]
[k (-> (.send client req (HttpResponse$BodyHandlers/ofString))
(body-data))])))
(into {}))))))))
(when-not test-utils/windows?
;; TODO: somehow doesn't work in Windows, should it?
(let [body "おはようございます!"]
(is (= body
(bb
'(do
(ns net
(:require
[cheshire.core :as json]
[clojure.java.io :as io])
(:import
(java.net URI)
(java.net.http HttpClient
HttpRequest
HttpRequest$BodyPublishers
HttpResponse$BodyHandlers)
(java.nio.charset Charset)
(java.util.function Supplier)))
(let [body "おはようございます!"
req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/post"))
(.method "POST" (HttpRequest$BodyPublishers/ofString
body (Charset/forName "UTF-16")))
(.header "Content-Type" "text/plain; charset=utf-16")
(.build))
client (HttpClient/newHttpClient)
res (.send client req (HttpResponse$BodyHandlers/ofString))]
(-> (.body res)
(json/parse-string true)
:data)))))))))
(deftest request-timeout-test
(is (str/includes?
(bb
'(do
(ns net
(:import
(java.net URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)
(java.time Duration)))
(let [client (HttpClient/newHttpClient)
req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/delay/1"))
(.GET)
(.timeout (Duration/ofMillis 200))
(.build))]
(try
(.send client req (HttpResponse$BodyHandlers/discarding))
(catch Throwable t
(-> (Throwable->map t)
:via
first
:type
name))))))
"TimeoutException")))
(deftest body-handlers-test
(is (= true
(bb
'(do
(ns net
(:require
[clojure.string :as str])
(:import
(java.net URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)
(java.nio.file Files StandardOpenOption)
(java.nio.file.attribute FileAttribute)))
(let [client (-> (HttpClient/newBuilder)
(.build))
uri (URI. "https://raw.githubusercontent.com/babashka/babashka/master/README.md")
req (-> (HttpRequest/newBuilder uri)
(.GET)
(.build))
temp-file (Files/createTempFile "bb-prefix-" "-bb-suffix" (make-array FileAttribute 0))
open-options (into-array StandardOpenOption [StandardOpenOption/CREATE
StandardOpenOption/WRITE])
handler (HttpResponse$BodyHandlers/ofFile temp-file open-options)
res (.send client req handler)
temp-file-path (str (.body res))
contents (slurp temp-file-path)]
(str/includes? contents "babashka")))))))
;; WebSockets
(defn ws-handler [{:keys [init] :as opts} req]
(when init (init req))
(httpkit.server/as-channel
req
(select-keys opts [:on-close :on-ping :on-receive])))
(def ^:dynamic *ws-port* 1234)
(defmacro with-ws-server
[opts & body]
`(let [s# (httpkit.server/run-server (partial ws-handler ~opts) {:port ~*ws-port*})]
(try ~@body (finally (s# :timeout 100)))))
(deftest websockets-test
(with-ws-server {:on-receive #(httpkit.server/send! %1 %2)}
(is (= "zomg websockets!"
(bb
'(do
(ns net
(:require
[clojure.string :as str])
(:import
(java.net URI)
(java.net.http HttpClient
WebSocket$Listener)
(java.util.concurrent CompletableFuture)
(java.util.function Function)))
(let [p (promise)
uri (URI. "ws://localhost:1234")
listener (reify WebSocket$Listener
(onOpen [_ ws]
(.request ws 1))
(onText [_ ws data last?]
(.request ws 1)
(.thenApply (CompletableFuture/completedFuture nil)
(reify Function
(apply [_ _] (deliver p (str data)))))))
client (HttpClient/newHttpClient)
ws (-> (.newWebSocketBuilder client)
(.buildAsync uri listener)
(deref))]
(.sendText ws "zomg websockets!" true)
(deref p 5000 ::timeout))))))))