diff --git a/deps.edn b/deps.edn index 6217666d..9c36d83e 100644 --- a/deps.edn +++ b/deps.edn @@ -51,11 +51,9 @@ :main-opts ["-m" "babashka.profile"]} :lib-tests {:extra-paths ["process/src" "process/test" "test-resources/lib_tests"] - :extra-deps {babashka/clj-http-lite - {:git/url "https://github.com/babashka/clj-http-lite" - :sha "f44ebe45446f0f44f2b73761d102af3da6d0a13e"} + :extra-deps {org.clj-commons/clj-http-lite {:mvn/version "0.4.392"} borkdude/spartan.spec {:git/url "https://github.com/borkdude/spartan.spec" - :sha "16f7eec4b6589c77c96c9fcf989f78fffcee7c4c"} + :sha "7689f85d4c79de6c7b5ee3ab62e3e72eeccd3318"} lambdaisland/regal {:git/url "https://github.com/lambdaisland/regal" :sha "f902d2c43121f9e1c48603d6eb99f5900eb6a9f6"} weavejester/medley {:git/url "https://github.com/weavejester/medley" @@ -92,7 +90,8 @@ java-http-clj/java-http-clj {:mvn/version "0.4.3"} com.stuartsierra/component {:mvn/version "1.0.0"} org.clojars.askonomm/ruuter {:mvn/version "1.2.2"} - org.clj-commons/digest {:mvn/version "1.4.100"}} + org.clj-commons/digest {:mvn/version "1.4.100"} + hato/hato {:mvn/version "0.8.2"}} :classpath-overrides {org.clojure/clojure nil org.clojure/spec.alpha nil org.clojure/core.specs.alpha nil}} diff --git a/sci b/sci index 3b624621..f3d2764b 160000 --- a/sci +++ b/sci @@ -1 +1 @@ -Subproject commit 3b62462112bde39d166914d0e424075ed18195cf +Subproject commit f3d2764b8f9fc14026c89e23d94fa2071ededcea diff --git a/src/babashka/impl/classes.clj b/src/babashka/impl/classes.clj index 01940c16..ee3413b2 100644 --- a/src/babashka/impl/classes.clj +++ b/src/babashka/impl/classes.clj @@ -2,87 +2,92 @@ {:no-doc true} (:require [babashka.impl.features :as features] + [babashka.impl.proxy :as proxy] [cheshire.core :as json] [sci.impl.types :as t])) +(def base-custom-map + `{clojure.lang.LineNumberingPushbackReader {:allPublicConstructors true + :allPublicMethods true} + java.lang.Thread + {:allPublicConstructors true + ;; generated with `public-declared-method-names`, see in + ;; `comment` below + :methods [{:name "activeCount"} + {:name "checkAccess"} + {:name "currentThread"} + {:name "dumpStack"} + {:name "enumerate"} + {:name "getAllStackTraces"} + {:name "getContextClassLoader"} + {:name "getDefaultUncaughtExceptionHandler"} + {:name "getId"} + {:name "getName"} + {:name "getPriority"} + {:name "getStackTrace"} + {:name "getState"} + {:name "getThreadGroup"} + {:name "getUncaughtExceptionHandler"} + {:name "holdsLock"} + {:name "interrupt"} + {:name "interrupted"} + {:name "isAlive"} + {:name "isDaemon"} + {:name "isInterrupted"} + {:name "join"} + {:name "run"} + {:name "setContextClassLoader"} + {:name "setDaemon"} + {:name "setDefaultUncaughtExceptionHandler"} + {:name "setName"} + {:name "setPriority"} + {:name "setUncaughtExceptionHandler"} + {:name "sleep"} + {:name "start"} + {:name "toString"} + {:name "yield"}]} + java.net.URL + {:allPublicConstructors true + :allPublicFields true + ;; generated with `public-declared-method-names`, see in + ;; `comment` below + :methods [{:name "equals"} + {:name "getAuthority"} + {:name "getContent"} + {:name "getDefaultPort"} + {:name "getFile"} + {:name "getHost"} + {:name "getPath"} + {:name "getPort"} + {:name "getProtocol"} + {:name "getQuery"} + {:name "getRef"} + {:name "getUserInfo"} + {:name "hashCode"} + {:name "openConnection"} + {:name "openStream"} + {:name "sameFile"} + ;; not supported: {:name "setURLStreamHandlerFactory"} + {:name "toExternalForm"} + {:name "toString"} + {:name "toURI"}]} + java.util.Arrays + {:methods [{:name "copyOf"} + {:name "copyOfRange"}]} + ;; this fixes clojure.lang.Reflector for Java 11 + java.lang.reflect.AccessibleObject + {:methods [{:name "canAccess"}]} + java.lang.reflect.Method + {:methods [{:name "getName"}]} + java.net.Inet4Address + {:methods [{:name "getHostAddress"}]} + java.net.Inet6Address + {:methods [{:name "getHostAddress"}]}}) + (def custom-map (cond-> - `{clojure.lang.LineNumberingPushbackReader {:allPublicConstructors true - :allPublicMethods true} - java.lang.Thread - {:allPublicConstructors true - ;; generated with `public-declared-method-names`, see in - ;; `comment` below - :methods [{:name "activeCount"} - {:name "checkAccess"} - {:name "currentThread"} - {:name "dumpStack"} - {:name "enumerate"} - {:name "getAllStackTraces"} - {:name "getContextClassLoader"} - {:name "getDefaultUncaughtExceptionHandler"} - {:name "getId"} - {:name "getName"} - {:name "getPriority"} - {:name "getStackTrace"} - {:name "getState"} - {:name "getThreadGroup"} - {:name "getUncaughtExceptionHandler"} - {:name "holdsLock"} - {:name "interrupt"} - {:name "interrupted"} - {:name "isAlive"} - {:name "isDaemon"} - {:name "isInterrupted"} - {:name "join"} - {:name "run"} - {:name "setContextClassLoader"} - {:name "setDaemon"} - {:name "setDefaultUncaughtExceptionHandler"} - {:name "setName"} - {:name "setPriority"} - {:name "setUncaughtExceptionHandler"} - {:name "sleep"} - {:name "start"} - {:name "toString"} - {:name "yield"}]} - java.net.URL - {:allPublicConstructors true - :allPublicFields true - ;; generated with `public-declared-method-names`, see in - ;; `comment` below - :methods [{:name "equals"} - {:name "getAuthority"} - {:name "getContent"} - {:name "getDefaultPort"} - {:name "getFile"} - {:name "getHost"} - {:name "getPath"} - {:name "getPort"} - {:name "getProtocol"} - {:name "getQuery"} - {:name "getRef"} - {:name "getUserInfo"} - {:name "hashCode"} - {:name "openConnection"} - {:name "openStream"} - {:name "sameFile"} - ;; not supported: {:name "setURLStreamHandlerFactory"} - {:name "toExternalForm"} - {:name "toString"} - {:name "toURI"}]} - java.util.Arrays - {:methods [{:name "copyOf"} - {:name "copyOfRange"}]} - ;; this fixes clojure.lang.Reflector for Java 11 - java.lang.reflect.AccessibleObject - {:methods [{:name "canAccess"}]} - java.lang.reflect.Method - {:methods [{:name "getName"}]} - java.net.Inet4Address - {:methods [{:name "getHostAddress"}]} - java.net.Inet6Address - {:methods [{:name "getHostAddress"}]}} + (merge base-custom-map + proxy/custom-reflect-map) features/hsqldb? (assoc `org.hsqldb.dbinfo.DatabaseInformationFull {:methods [{:name "" :parameterTypes ["org.hsqldb.Database"]}]} @@ -189,6 +194,7 @@ java.net.CookieHandler java.net.CookieManager java.net.CookieStore + java.net.CookiePolicy java.net.HttpCookie java.net.PasswordAuthentication java.net.ProxySelector @@ -204,15 +210,21 @@ java.net.http.HttpResponse java.net.http.HttpResponse$BodyHandler java.net.http.HttpResponse$BodyHandlers + java.net.http.HttpTimeoutException java.net.http.WebSocket java.net.http.WebSocket$Builder java.net.http.WebSocket$Listener java.security.cert.X509Certificate javax.crypto.Mac javax.crypto.spec.SecretKeySpec + javax.net.ssl.HostnameVerifier ;; clj-http-lite + javax.net.ssl.HttpsURLConnection ;; clj-http-lite + javax.net.ssl.KeyManagerFactory javax.net.ssl.SSLContext javax.net.ssl.SSLParameters + javax.net.ssl.SSLSession ;; clj-http-lite javax.net.ssl.TrustManager + javax.net.ssl.TrustManagerFactory javax.net.ssl.X509TrustManager jdk.internal.net.http.HttpClientBuilderImpl jdk.internal.net.http.HttpClientFacade @@ -259,6 +271,7 @@ java.security.MessageDigest java.security.DigestInputStream java.security.Provider + java.security.KeyStore java.security.SecureRandom java.security.Security java.sql.Date @@ -337,6 +350,7 @@ java.util.concurrent.TimeUnit java.util.function.Function java.util.function.Supplier + java.util.zip.Inflater java.util.zip.InflaterInputStream java.util.zip.DeflaterInputStream java.util.zip.GZIPInputStream @@ -344,6 +358,7 @@ java.util.zip.ZipInputStream java.util.zip.ZipOutputStream java.util.zip.ZipEntry + java.util.zip.ZipException java.util.zip.ZipFile ~(symbol "[B") ~(symbol "[I") diff --git a/src/babashka/impl/proxy.clj b/src/babashka/impl/proxy.clj index 3ac22e80..b4341d06 100644 --- a/src/babashka/impl/proxy.clj +++ b/src/babashka/impl/proxy.clj @@ -61,9 +61,32 @@ ["java.net.Authenticator" #{}] (proxy [java.net.Authenticator] [] - (getPasswordAuthentication [] ((method-or-bust methods 'getPasswordAuthentication) this))) + (getPasswordAuthentication [] + ((method-or-bust methods 'getPasswordAuthentication) this))) ["java.net.ProxySelector" #{}] (proxy [java.net.ProxySelector] [] - (connectFailed [_ _ _] ((method-or-bust methods 'connectFailed) this)) - (select [_ _] ((method-or-bust methods 'select) this)))))) + (connectFailed [uri socket-address ex] + ((method-or-bust methods 'connectFailed) this uri socket-address ex)) + (select [uri] ((method-or-bust methods 'select) this uri))) + + ["javax.net.ssl.HostnameVerifier" #{}] + (proxy [javax.net.ssl.HostnameVerifier] [] + (verify [host-name session] ((method-or-bust methods 'verify) this host-name session)))))) + +(defn class-sym [c] (symbol (class-name c))) + +(def custom-reflect-map + {(class-sym (class (proxy-fn {:class java.net.Authenticator}))) + {:methods [{:name "getPasswordAuthentication"}]} + (class-sym (class (proxy-fn {:class java.net.ProxySelector}))) + {:methods [{:name "connectFailed"} + {:name "select"}]} + (class-sym (class (proxy-fn {:class javax.net.ssl.HostnameVerifier}))) + {:methods [{:name "verify"}]}}) + +;;; Scratch + +(comment + + ) diff --git a/test-resources/lib_tests/babashka/run_all_libtests.clj b/test-resources/lib_tests/babashka/run_all_libtests.clj index 01c215eb..1b54e373 100644 --- a/test-resources/lib_tests/babashka/run_all_libtests.clj +++ b/test-resources/lib_tests/babashka/run_all_libtests.clj @@ -1,7 +1,10 @@ (ns babashka.run-all-libtests (:require [clojure.java.io :as io] [clojure.string :as str] - [clojure.test :as t])) + [clojure.test :as t] + [spartan.spec])) + +(require 'clojure.spec.alpha) (def ns-args (set (map symbol *command-line-args*))) @@ -260,6 +263,8 @@ (test-namespaces 'clj-commons.digest-test) +(test-namespaces 'hato.client-test) + ;;;; final exit code (let [{:keys [:test :fail :error] :as m} @status] diff --git a/test-resources/lib_tests/clj_http/lite/client_test.clj b/test-resources/lib_tests/clj_http/lite/client_test.clj index d61114c6..d6a2ba89 100644 --- a/test-resources/lib_tests/clj_http/lite/client_test.clj +++ b/test-resources/lib_tests/clj_http/lite/client_test.clj @@ -1,25 +1,34 @@ (ns clj-http.lite.client-test (:require [cheshire.core :as json] [clj-http.lite.client :as client] - [clojure.test :as t :refer [deftest]])) + [clojure.test :as t :refer [deftest is]])) (deftest client-test - (prn (:status (client/get "https://www.clojure.org" {:throw-exceptions false}))) + (is (= 200 (:status (client/get "https://www.clojure.org" {:throw-exceptions false})))) - (prn (:status (client/get "https://postman-echo.com/get?foo1=bar1&foo2=bar2" {:throw-exceptions false}))) + (is (= 200 (:status (client/get "https://postman-echo.com/get?foo1=bar1&foo2=bar2" {:throw-exceptions false})))) - (prn (:status (client/post "https://postman-echo.com/post" {:throw-exceptions false}))) + (is (= 200 (:status (client/post "https://postman-echo.com/post" {:throw-exceptions false})))) - (prn (:status (client/post "https://postman-echo.com/post" - {:body (json/generate-string {:a 1}) - :headers {"X-Hasura-Role" "admin"} - :content-type :json - :accept :json - :throw-exceptions false}))) + (is (= 200 (:status (client/post "https://postman-echo.com/post" + {:body (json/generate-string {:a 1}) + :headers {"X-Hasura-Role" "admin"} + :content-type :json + :accept :json + :throw-exceptions false})))) - (prn (:status (client/put "https://postman-echo.com/put" - {:body (json/generate-string {:a 1}) - :headers {"X-Hasura-Role" "admin"} - :content-type :json - :accept :json - :throw-exceptions false})))) + (is (= 200 (:status (client/put "https://postman-echo.com/put" + {:body (json/generate-string {:a 1}) + :headers {"X-Hasura-Role" "admin"} + :content-type :json + :accept :json + :throw-exceptions false}))))) + +(deftest insecure-test + (is (= 200 (:status (client/get "https://self-signed.badssl.com/" {:insecure? true}))))) + +(deftest exception-test + (try (client/get "https://site.com/broken") + (is false "should not reach here") + (catch Exception e + (is (:headers (ex-data e)))))) diff --git a/test-resources/lib_tests/hato/client_test.clj b/test-resources/lib_tests/hato/client_test.clj new file mode 100644 index 00000000..d17d7b55 --- /dev/null +++ b/test-resources/lib_tests/hato/client_test.clj @@ -0,0 +1,348 @@ +(ns hato.client-test + (:refer-clojure :exclude [get]) + (:require [clojure.test :refer :all] + [hato.client :refer :all] + [clojure.java.io :as io] + [org.httpkit.server :as http-kit] + [cheshire.core :as json] + [cognitect.transit :as transit] + #_[promesa.exec :as pexec] + #_[ring.middleware.multipart-params]) + (:import (java.io InputStream ByteArrayOutputStream) + (java.net ProxySelector CookieHandler CookieManager) + (java.net.http HttpClient$Redirect HttpClient$Version HttpClient) + (java.time Duration) + (javax.net.ssl SSLContext) + (java.util UUID))) + +(defmacro with-server + "Spins up a local HTTP server with http-kit." + [handler & body] + `(let [s# (http-kit/run-server ~handler {:port 1234})] + (try ~@body + (finally + (s# :timeout 100))))) + +(deftest test-build-http-client + (testing "authenticator" + (is (.isEmpty (.authenticator (build-http-client {}))) "not set by default") + (is (= "user" (-> (build-http-client {:authenticator {:user "user" :pass "pass"}}) + .authenticator .get .getPasswordAuthentication .getUserName))) + (is (.isEmpty (.authenticator (build-http-client {:authenticator :some-invalid-value}))) "ignore invalid input")) + + (testing "connect-timeout" + (is (.isEmpty (.connectTimeout (build-http-client {}))) "not set by default") + (is (= 5 (-> (build-http-client {:connect-timeout 5}) (.connectTimeout) ^Duration (.get) (.toMillis)))) + (is (thrown? Exception (build-http-client {:connect-timeout :not-a-number})))) + + (testing "cookie-manager and cookie-policy" + (is (.isEmpty (.cookieHandler (build-http-client {}))) "not set by default") + (are [x] (instance? CookieHandler (-> ^HttpClient (build-http-client {:cookie-policy x}) (.cookieHandler) (.get))) + :none + :all + :original-server + :any-random-thing ; Invalid values are ignored, so the default :original-server will be in effect + ) + + (let [cm (CookieManager.)] + (is (= cm (-> (build-http-client {:cookie-handler cm :cookie-policy :all}) (.cookieHandler) (.get))) + ":cookie-handler takes precedence over :cookie-policy"))) + + (testing "redirect-policy" + (is (= HttpClient$Redirect/NEVER (.followRedirects (build-http-client {}))) "NEVER by default") + (are [expected option] (= expected (.followRedirects (build-http-client {:redirect-policy option}))) + HttpClient$Redirect/ALWAYS :always + HttpClient$Redirect/NEVER :never + HttpClient$Redirect/NORMAL :normal) + (is (thrown? Exception (build-http-client {:redirect-policy :not-valid-value})))) + + (testing "priority" + (is (build-http-client {:priority 1})) + (is (build-http-client {:priority 256})) + (is (thrown? Exception (build-http-client {:priority :not-a-number}))) + (are [x] (thrown? Exception (build-http-client {:priority x})) + :not-a-number + 0 + 257)) + + (testing "proxy" + (is (.isEmpty (.proxy (build-http-client {}))) "not set by default") + (is (.isPresent (.proxy (build-http-client {:proxy :no-proxy})))) + (is (.isPresent (.proxy (build-http-client {:proxy (ProxySelector/getDefault)}))))) + + #_(testing "executor" + (let [executor (pexec/fixed-pool 1) + client (build-http-client {:executor executor}) + stored-executor (.orElse (.executor client) nil)] + (is (instance? java.util.concurrent.ThreadPoolExecutor stored-executor) "executor has proper type") + (is (= executor stored-executor) "executor set properly"))) + + (testing "ssl-context" + (is (= (SSLContext/getDefault) (.sslContext (build-http-client {})))) + (is (not= (SSLContext/getDefault) (.sslContext (build-http-client {:ssl-context {:keystore (io/resource "keystore.p12") + :keystore-pass "borisman" + :trust-store (io/resource "keystore.p12") + :trust-store-pass "borisman"}}))))) + + (testing "version" + (is (= HttpClient$Version/HTTP_2 (.version (build-http-client {}))) "HTTP_2 by default") + (are [expected option] (= expected (.version (build-http-client {:version option}))) + HttpClient$Version/HTTP_1_1 :http-1.1 + HttpClient$Version/HTTP_2 :http-2) + (is (thrown? Exception (build-http-client {:version :not-valid-value}))))) + +(deftest ^:integration test-basic-response + (testing "basic get request returns response map" + (let [r (get "https://httpbin.org/get")] + (is (pos-int? (:request-time r))) + (is (= 200 (:status r))) + (is (= "https://httpbin.org/get" (:uri r))) + (is (= :http-2 (:version r))) + (is (= :get (-> r :request :request-method))) + (is (= "gzip, deflate" (get-in r [:request :headers "accept-encoding"]))))) + + #_(testing "setting executor works" + (let [c (build-http-client {:executor (pexec/fixed-pool 1)}) + r (get "https://httpbin.org/get" {:http-client c})] + (is (= 200 (:status r))))) + + (testing "query encoding" + (let [r (get "https://httpbin.org/get?foo=bar params :file :tempfile slurp)} + :form (select-keys params [:Content/type :eggplant :title])}))})) + + (let [uuid (.toString (UUID/randomUUID)) + _ (spit (io/file ".test-data") uuid) + r (post "http://localhost:1234" {:as :json + :multipart [{:name "title" :content "My Awesome Picture"} + {:name "Content/type" :content "image/jpeg"} + {:name "foo.txt" :part-name "eggplant" :content "Eggplants"} + {:name "file" :content (io/file ".test-data")}]})] + + (is (= {:files {:file uuid} + :form {:Content/type "image/jpeg" + :eggplant "Eggplants" + :title "My Awesome Picture"}} (:body r))))))) + +(deftest ^:integration test-basic-response-async + (testing "basic request returns something" + (let [r @(request {:url "https://httpbin.org/get" :async? true})] + (is (= 200 (:status r))))) + + (testing "basic get request returns response map" + (let [r @(get "https://httpbin.org/get" {:async? true})] + (is (pos-int? (:request-time r))) + (is (= 200 (:status r))) + (is (= "https://httpbin.org/get" (:uri r))) + (is (= :http-2 (:version r))) + (is (= :get (-> r :request :request-method))) + (is (= "gzip, deflate" (get-in r [:request :headers "accept-encoding"]))))) + + (testing "callback" + (is (= 200 @(get "https://httpbin.org/status/200" {:async? true} :status identity)) + "returns status on success") + (is (= 400 @(get "https://httpbin.org/status/400" {:async? true} identity #(-> % ex-data :status))) + "extracts response map from ex-info on fail"))) + +(deftest ^:integration test-exceptions + (testing "throws on exceptional status" + (is (thrown? Exception (get "https://httpbin.org/status/500")))) + + (testing "can opt out" + (is (= 500 (:status (get "https://httpbin.org/status/500" {:throw-exceptions false}))))) + + (testing "async" + (is (thrown? Exception @(get "https://httpbin.org/status/400" {:async? true})) + "default callbacks throws on error") + (is (= 400 (:status @(get "https://httpbin.org/status/400" {:async? true :throw-exceptions? false}))) + "default callbacks does not throw if :throw-exceptions? is false"))) + +(deftest ^:integration test-coercions + (testing "as default" + (let [r (get "https://httpbin.org/get")] + (is (string? (:body r))))) + + (testing "as byte array" + (let [r (get "https://httpbin.org/get" {:as :byte-array})] + (is (instance? (Class/forName "[B") (:body r))) + (is (string? (String. ^bytes (:body r)))))) + + (testing "as stream" + (let [r (get "https://httpbin.org/get" {:as :stream})] + (is (instance? InputStream (:body r))) + (is (string? (-> r :body slurp))))) + + (testing "as string" + (let [r (get "https://httpbin.org/get" {:as :string})] + (is (string? (:body r))))) + + #_(testing "as auto" + (let [r (get "https://httpbin.org/get" {:as :auto})] + (is (coll? (:body r))))) + + (testing "as json" + (let [r (get "https://httpbin.org/get" {:as :json})] + (is (coll? (:body r))))) + + (testing "large json" + ; Ensure large json can be parsed without the stream closing prematurely. + (with-server (fn app [_] + {:status 200 + :headers {"Content-Type" "application/json"} + :body (json/generate-string (take 1000 (repeatedly (constantly {:a 1}))))}) + + (let [b (:body (get "http://localhost:1234" {:as :json}))] + (is (coll? b)) + (is (= 1000 (count b)))))) + + (doseq [content-type [#_"msgpack" "json"]] + (testing (str "decode " content-type) + (let [body {:a [1 2]}] + (with-server (fn app [_] + {:status 200 + :headers {"Content-Type" (str "application/transit+" content-type)} + :body (let [output (ByteArrayOutputStream.)] + (transit/write (transit/writer output (keyword content-type)) body) + (clojure.java.io/input-stream (.toByteArray output)))}) + + (let [r (get "http://localhost:1234" {:as :auto})] + (is (= body (:body r))))))) + + (testing (str "empty stream when decoding " content-type) + (with-server (fn app [_] + {:status 200 + :headers {"Content-Type" (str "application/transit+" content-type)} + :body nil}) + + (let [r (get "http://localhost:1234" {:as :auto})] + (is (nil? (:body r)))))))) + +(deftest ^:integration test-auth + (testing "authenticator basic auth (non-preemptive) via client option" + (let [r (get "https://httpbin.org/basic-auth/user/pass" {:http-client {:authenticator {:user "user" :pass "pass"}}})] + (is (= 200 (:status r)))) + + (is (thrown? Exception (get "https://httpbin.org/basic-auth/user/pass" {:http-client {:authenticator {:user "user" :pass "invalid"}}})))) + + (testing "basic auth" + (let [r (get "https://httpbin.org/basic-auth/user/pass" {:basic-auth {:user "user" :pass "pass"}})] + (is (= 200 (:status r)))) + + (let [r (get "https://user:pass@httpbin.org/basic-auth/user/pass")] + (is (= 200 (:status r)))) + + (is (thrown? Exception (get "https://httpbin.org/basic-auth/user/pass" {:basic-auth {:user "user" :pass "invalid"}}))))) + +(deftest ^:integration test-redirects + ; Changed provider due to https://github.com/postmanlabs/httpbin/issues/617 + (let [redirect-to "https://httpbingo.org/get" + uri (format "https://httpbingo.org/redirect-to?url=%s" redirect-to)] + (testing "no redirects (default)" + (let [r (get uri {:as :string})] + (is (= 302 (:status r))) + (is (= uri (:uri r))))) + + (testing "explicitly never" + (let [r (get uri {:as :string :http-client {:redirect-policy :never}})] + (is (= 302 (:status r))) + (is (= uri (:uri r))))) + + (testing "always redirect" + (let [r (get uri {:as :string :http-client {:redirect-policy :always}})] + (is (= 200 (:status r))) + (is (= redirect-to (:uri r))))) + + (testing "normal redirect (same protocol - accepted)" + (let [r (get uri {:as :string :http-client {:redirect-policy :normal}})] + (is (= 200 (:status r))) + (is (= redirect-to (:uri r))))) + + (testing "normal redirect (different protocol - denied)" + (let [https-tp-http-uri (format "https://httpbingo.org/redirect-to?url=%s" "http://httpbingo.org/get") + r (get https-tp-http-uri {:as :string :http-client {:redirect-policy :normal}})] + (is (= 302 (:status r))) + (is (= https-tp-http-uri (:uri r))))) + + (testing "default max redirects" + (are [status redirects] (= status (:status (get (str "https://httpbingo.org/redirect/" redirects) {:http-client {:redirect-policy :normal}}))) + 200 4 + 302 5)))) + +(deftest ^:integration test-cookies + (testing "no cookie manager" + (let [r (get "https://httpbin.org/cookies/set/moo/cow" {:as :json :http-client {:redirect-policy :always}})] + (is (= 200 (:status r))) + (is (nil? (-> r :body :cookies :moo))))) + + (testing "with cookie manager" + (let [r (get "https://httpbin.org/cookies/set/moo/cow" {:as :json :http-client {:redirect-policy :always :cookie-policy :all}})] + (is (= 200 (:status r))) + (is (= "cow" (-> r :body :cookies :moo))))) + + (testing "persists over requests" + (let [c (build-http-client {:redirect-policy :always :cookie-policy :all}) + _ (get "https://httpbin.org/cookies/set/moo/cow" {:http-client c}) + r (get "https://httpbin.org/cookies" {:as :json :http-client c})] + (is (= 200 (:status r))) + (is (= "cow" (-> r :body :cookies :moo)))))) + +(deftest ^:integration test-decompression + (testing "gzip via byte array" + (let [r (get "https://httpbin.org/gzip" {:as :json})] + (is (= 200 (:status r))) + (is (true? (-> r :body :gzipped))))) + + (testing "gzip via stream" + (let [r (get "https://httpbin.org/gzip" {:as :stream})] + (is (= 200 (:status r))) + (is (instance? InputStream (:body r))))) + + (testing "deflate via byte array" + (let [r (get "https://httpbin.org/deflate" {:as :json})] + (is (= 200 (:status r))) + (is (true? (-> r :body :deflated))))) + + (testing "deflate via stream" + (let [r (get "https://httpbin.org/deflate" {:as :stream})] + (is (= 200 (:status r))) + (is (instance? InputStream (:body r)))))) + +(deftest ^:integration test-http2 + (testing "can make an http2 request" + (let [r (get "https://nghttp2.org/httpbin/get" {:as :json})] + (is (= :http-2 (:version r)))))) + +(deftest custom-middleware + (let [r (get "" {:middleware [(fn [client] + (fn [req] + ::response))]})] + (is (= ::response r)))) + +(try (get "https://httpbin.org/gzip" {:timeout 1000}) + (catch java.net.http.HttpTimeoutException _ + (doseq [v (vals (ns-publics *ns*))] + (when (:integration (meta v)) + (println "Removing test from" v "because httpbin is slow.") + (alter-meta! v dissoc :test))))) diff --git a/test-resources/lib_tests/spartan/spec_test.clj b/test-resources/lib_tests/spartan/spec_test.clj index e08e02b1..a0009a42 100644 --- a/test-resources/lib_tests/spartan/spec_test.clj +++ b/test-resources/lib_tests/spartan/spec_test.clj @@ -1,8 +1,8 @@ (ns spartan.spec-test (:require [clojure.test :as t :refer [deftest is]])) -(time (require '[spartan.spec :as s])) -(require '[spartan.spec :as s]) +(time (require '[spartan.spec])) +(require '[clojure.spec.alpha :as s]) (deftest spec-test (time (s/explain (s/cat :i int? :s string?) [1 :foo])) diff --git a/test/babashka/main_test.clj b/test/babashka/main_test.clj index 63d98a06..dd892032 100644 --- a/test/babashka/main_test.clj +++ b/test/babashka/main_test.clj @@ -793,10 +793,19 @@ true"))) (is (= "192.168.2.2" (bb nil "(-> (java.net.InetAddress/getByName \"192.168.2.2\") (.getHostAddress))")))) (deftest satisfies-protocols-test - #_(is (true? (bb nil "(satisfies? clojure.core.protocols/Datafiable {})"))) - #_(is (true? (bb nil "(satisfies? clojure.core.protocols/Navigable {})"))) + (is (true? (bb nil "(satisfies? clojure.core.protocols/Datafiable {})"))) + (is (true? (bb nil "(satisfies? clojure.core.protocols/Navigable {})"))) (is (true? (bb nil "(satisfies? clojure.core.protocols/IKVReduce {})")))) +(deftest interop-on-proxy + (is (true? (bb nil (pr-str + '(instance? java.net.PasswordAuthentication + (.getPasswordAuthentication + (proxy [java.net.Authenticator] [] + (getPasswordAuthentication [] + (java.net.PasswordAuthentication. "bork" + (char-array "dude"))))))))))) + ;;;; Scratch (comment