diff --git a/README.md b/README.md index 7a7f91a..5d5eaa6 100644 --- a/README.md +++ b/README.md @@ -141,5 +141,107 @@ e.g. (wmk/with-stubs [{:req [:GET "/ping"] :res [200] :scenario "myscenario" :state {:required "Started" :new "pinged"}}] ...) ``` +## Configuration + +You can configure WireMock with the same options available to the Java library (as of version 2.27.2). + +### Network ports and binding + +```clj +:port 8000 +:https-port 8001 +:dynamic-port? true +:dynamic-https-port? true +:bind-address "192.168.1.111" +``` + +### Jetty Configuration + +```clj +:container-threads 5 +:jetty-acceptors 4 +:jetty-accept-queue-size 100 +:jetty-header-buffer-size 16834 +:asynchronous-response-enabled? true +:asynchronous-response-threads 10 +``` + +### HTTPS Configuration + +```clj +:keystore-path "/path/to/https-certs-keystore.jks" +:keystore-password "verysecret!" +:keystore-type "BKS" +:key-manager-password "donttell" +:need-client-auth? true +:trust-store-path "/path/to/trust-store.jks" +:trust-store-password "trustme" +``` + +### Proxy Settings + +```clj +:enable-browser-proxying? true +:preserve-host-header? false +:proxy-host-header "my.otherdomain.com" +:proxy-via ["my.corporate.proxy", 8080] +:ca-keystore-path "/path/to/ca-key-store.jks" +:ca-keystore-password "trustme" +:ca-keystore-type "JKS" +``` + +### File Locations + +```clj +:using-files-under-directory "/path/to/files-and-mappings-root" +:using-files-under-classpath "root/path/under/classpath" +``` + +### Request Journal + +```clj +:disable-request-journal? true +:max-requests-journal-entries 100 +``` + +### Notification + +```clj +:log-to-console? true ;syntactic sugar for .notifier(new ConsoleNotifier(true)) +:notifier (reify Notifier + (info [_ _]) + (error [_ _]) + (error [_ _ _]) +``` + +### Gzip + +```clj +:gzip-disabled? true +``` + +### Extensions +Supports passing instances of Extension only at this point. + +```clj +:extensions [(ResponseTemplateTransformer. true)] +``` + +### Transfer Encoding + +Accepts `:always`, `:body-file`, or `:never`. + +```clj +:use-chunked-transfer-encoding :body-file +``` + +### Cross-origin response headers (CORS) + +```clj +:stub-cors-enabled? true +``` + +You can find more information about each configuration on the [WireMock Configuration](http://wiremock.org/docs/configuration) page. + ## License [Eclipse Public License 1.0](http://opensource.org/licenses/eclipse-1.0.php), the same as Clojure. \ No newline at end of file diff --git a/project.clj b/project.clj index c299504..b5feafc 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject kelveden/clj-wiremock "1.8.0" +(defproject kelveden/clj-wiremock "1.9.0-SNAPSHOT" :description "Clojure bindings for WireMock" :url "https://github.com/kelveden/clj-wiremock" :license {:name "Eclipse Public License - v 1.0" diff --git a/src/clj_wiremock/server.clj b/src/clj_wiremock/server.clj index 5ff3321..5d6c4d9 100644 --- a/src/clj_wiremock/server.clj +++ b/src/clj_wiremock/server.clj @@ -3,7 +3,9 @@ [clj-http.client :as http] [clj-wiremock.stub :refer [->stub]] [clojure.tools.logging :as log]) - (:import com.github.tomakehurst.wiremock.core.WireMockConfiguration)) + (:import (com.github.tomakehurst.wiremock.core WireMockConfiguration Options$ChunkedEncodingPolicy) + (com.github.tomakehurst.wiremock.common ConsoleNotifier) + (com.github.tomakehurst.wiremock.extension Extension))) (defprotocol Wiremocked (start! [_] "Start the wiremock server.") @@ -49,10 +51,87 @@ (json/parse-string true) :requests))) +(defn- chunked-encoding-policy + "Gets the ChunkedEncodingPolicy according to the given keyword." + [policy] + (case policy + :always (Options$ChunkedEncodingPolicy/ALWAYS) + :body-file (Options$ChunkedEncodingPolicy/BODY_FILE) + :never (Options$ChunkedEncodingPolicy/NEVER) + (throw (IllegalArgumentException. (str policy " is not a recognizable chunked encoding policy. (try :always, :body-file, or :never instead)"))))) + +(defn- set-wiremock-option + "Sets the WireMock configuration according to the given key/value pair." + [^WireMockConfiguration config key value] + (case key + ;; Network ports and binding + :port (.port config value) + :https-port (.httpsPort config (Integer/valueOf value)) + :dynamic-port? (if value (.dynamicPort config) config) + :dynamic-https-port? (if value (.dynamicHttpsPort config) config) + :bind-address (.bindAddress config value) + + ;; Jetty Configuration + :container-threads (.containerThreads config (Integer/valueOf value)) + :jetty-acceptors (.jettyAcceptors config (Integer/valueOf value)) + :jetty-accept-queue-size (.jettyAcceptQueueSize config (Integer/valueOf value)) + :jetty-header-buffer-size (.jettyHeaderBufferSize config (Integer/valueOf value)) + :asynchronous-response-enabled? (.asynchronousResponseEnabled config value) + :asynchronous-response-threads (.asynchronousResponseThreads config value) + + ;; HTTPS Configuration + :keystore-path (.keystorePath config value) + :keystore-password (.keystorePassword config value) + :keystore-type (.keystoreType config value) + :key-manager-password (.keyManagerPassword config value) + :need-client-auth? (.needClientAuth config value) + :trust-store-path (.trustStorePath config value) + :trust-store-password (.trustStorePassword config value) + + ;; Proxy Settings + :enable-browser-proxying? (.enableBrowserProxying config value) + :preserve-host-header? (.preserveHostHeader config value) + :proxy-host-header (.proxyHostHeader config value) + :proxy-via (.proxyVia config (first value) (second value)) + :ca-keystore-path (.caKeystorePath config value) + :ca-keystore-password (.caKeystorePassword config value) + :ca-keystore-type (.caKeystoreType config value) + + ;; File Locations + :using-files-under-directory (.usingFilesUnderDirectory config value) + :using-files-under-classpath (.usingFilesUnderClasspath config value) + + ;; Request Journal + :disable-request-journal? (if value (.disableRequestJournal config) config) + :max-request-journal-entries (.maxRequestJournalEntries config value) + + ;; Notification (Logging) + :log-to-console? (if value (.notifier config (ConsoleNotifier. true)) config) + :notifier (.notifier config value) + + ;; Gzip + :gzip-disabled? (.gzipDisabled config value) + + ;; Extensions - supports passing instances of Extension only at this point + :extensions (.extensions config (into-array Extension value)) + + ;; Transfer Encoding + :use-chunked-transfer-encoding (.useChunkedTransferEncoding config (chunked-encoding-policy value)) + + ;; Cross-origin response headers (CORS) + :stub-cors-enabled? (.stubCorsEnabled config value) + + ;; Unrecognizable Option + (throw (IllegalArgumentException. (str key " is not a recognizable clj-wiremock option."))))) + +(defn- wiremock-config + "Creates a WireMock configuration with the given options." + [options] + (reduce-kv set-wiremock-option (WireMockConfiguration.) options)) + (defn init-wiremock - "Intialises a new WireMock server ready for starting on the specified port." - [{:keys [port]}] - (let [config (doto (new WireMockConfiguration) - (.port (int port))) - wmk-java (new com.github.tomakehurst.wiremock.WireMockServer config)] - (->WireMockServer wmk-java))) + "Intialises a new WireMock server ready for starting with the given options." + [options] + (let [config (wiremock-config options) + wmk-java (com.github.tomakehurst.wiremock.WireMockServer. config)] + (->WireMockServer wmk-java))) \ No newline at end of file diff --git a/test/clj_wiremock/test/server_test.clj b/test/clj_wiremock/test/server_test.clj index c2801c9..7d0e206 100644 --- a/test/clj_wiremock/test/server_test.clj +++ b/test/clj_wiremock/test/server_test.clj @@ -3,7 +3,10 @@ [clj-wiremock.test.helpers :refer [ping-stub ping-url get-free-port]] [clojure.test :refer :all] [clj-http.client :as http]) - (:import (java.net ConnectException))) + (:import (java.net ConnectException) + (com.github.tomakehurst.wiremock.extension.responsetemplating ResponseTemplateTransformer) + (com.github.tomakehurst.wiremock.core Options$ChunkedEncodingPolicy) + (com.github.tomakehurst.wiremock.common ConsoleNotifier Slf4jNotifier Notifier))) (deftest can-start-and-stop-wiremock (let [port (get-free-port) @@ -81,4 +84,173 @@ (is (= 1 (count requests))) (is (= "/ping" (get-in request [:request :url])))) - (finally (server/stop! wiremock))))) \ No newline at end of file + (finally (server/stop! wiremock))))) + +(deftest can-set-general-network-options + (let [port 18080 + https-port 10443 + bind-address "192.0.1.99" + wiremock (server/init-wiremock {:port port + :https-port https-port + :bind-address bind-address})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (= port (-> options (.portNumber)))) + (is (= https-port (-> options (.httpsSettings) (.port)))) + (is (= bind-address (-> options (.bindAddress))))))) + +(deftest can-set-dynamic-port-options + (let [wiremock (server/init-wiremock {:dynamic-port? true + :dynamic-https-port? true})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (= 0 (-> options (.portNumber)))) + (is (= 0 (-> options (.httpsSettings) (.port))))))) + +(deftest do-not-set-dynamic-port-options-when-option-is-false + (let [wiremock (server/init-wiremock {:dynamic-port? false + :dynamic-https-port? false})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (= 8080 (-> options (.portNumber)))) + (is (= -1 (-> options (.httpsSettings) (.port))))))) + +(deftest can-set-jetty-options + (let [container-threads 11 + acceptors 22 + accept-queue-size 33 + header-buffer-size 44 + asynchronous-response-threads 55 + wiremock (server/init-wiremock {:container-threads container-threads + :jetty-acceptors acceptors + :jetty-accept-queue-size accept-queue-size + :jetty-header-buffer-size header-buffer-size + :asynchronous-response-enabled? true + :asynchronous-response-threads asynchronous-response-threads})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (= container-threads (-> options (.containerThreads)))) + (is (= acceptors (-> options (.jettySettings) (.getAcceptors) (.get)))) + (is (= accept-queue-size (-> options (.jettySettings) (.getAcceptQueueSize) (.get)))) + (is (= header-buffer-size (-> options (.jettySettings) (.getRequestHeaderSize) (.get)))) + (is (= true (-> options (.getAsynchronousResponseSettings) (.isEnabled)))) + (is (= asynchronous-response-threads (-> options (.getAsynchronousResponseSettings) (.getThreads))))))) + +(deftest can-set-https-options + (let [keystore-password "keystore-secret" + keystore-type "PKCS12" + key-manager-password "key-manager-secret" + trust-store-password "trust-store-secret" + wiremock (server/init-wiremock {;:keystore-path keystore-path TODO: How can we test it? + :keystore-password keystore-password + :keystore-type keystore-type + :key-manager-password key-manager-password + :need-client-auth? true + ;:trust-store-path trust-store-path TODO: How can we test it? + :trust-store-password trust-store-password})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (= keystore-password (-> options (.httpsSettings) (.keyStorePassword)))) + (is (= keystore-type (-> options (.httpsSettings) (.keyStoreType)))) + (is (= key-manager-password (-> options (.httpsSettings) (.keyManagerPassword)))) + (is (= trust-store-password (-> options (.httpsSettings) (.trustStorePassword))))))) + +(deftest can-set-proxy-options + (let [proxy-via-host "my.corporate.proxy" + proxy-host-header "my.otherdomain.com" + proxy-via-port 8080 + ca-keystore-password "trustme" + ca-keystore-type "JKS" + wiremock (server/init-wiremock {:enable-browser-proxying? true + :preserve-host-header? false + :proxy-host-header proxy-host-header + :ca-keystore-password ca-keystore-password + :ca-keystore-type ca-keystore-type + ;:ca-keystore-path ca-keystore-path TODO: How can we test it? + :proxy-via [proxy-via-host proxy-via-port]})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (= true (-> options (.browserProxyingEnabled)))) + (is (= false (-> options (.shouldPreserveHostHeader)))) + (is (= proxy-host-header (-> options (.proxyHostHeader)))) + (is (= proxy-via-host (-> options (.proxyVia) (.host)))) + (is (= proxy-via-port (-> options (.proxyVia) (.port)))) + (is (= ca-keystore-password (-> options (.browserProxySettings) (.caKeyStore) (.password)))) + (is (= ca-keystore-type (-> options (.browserProxySettings) (.caKeyStore) (.type))))))) + +(deftest can-set-file-location-options-using-directory + (let [directory "/path/to/directory" + wiremock (server/init-wiremock {:using-files-under-directory directory})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (= directory (-> options (.filesRoot) (.getPath))))))) + +(deftest can-set-file-location-options-using-classpath + (let [classpath "/path/into/classpath" + wiremock (server/init-wiremock {:using-files-under-classpath classpath})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (= classpath (-> options (.filesRoot) (.getPath))))))) + +(deftest can-set-request-journal-options + (let [max-request-journal-entries 100 + wiremock (server/init-wiremock {:disable-request-journal? true + :max-request-journal-entries max-request-journal-entries})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (= true (-> options (.requestJournalDisabled)))) + (is (= max-request-journal-entries (-> options (.maxRequestJournalEntries) (.get))))))) + +(deftest can-set-extensions-option + (let [wiremock (server/init-wiremock {:extensions [(ResponseTemplateTransformer. true)]})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (not (empty? (-> options (.extensionsOfType ResponseTemplateTransformer)))))))) + +(deftest can-log-to-console-option + (let [wiremock (server/init-wiremock {:log-to-console? true})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (instance? ConsoleNotifier (-> options (.notifier))))))) + +(deftest do-not-set-log-to-console-option-when-false + (let [wiremock (server/init-wiremock {:log-to-console? false})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (instance? Slf4jNotifier (-> options (.notifier))))))) + +(deftest can-set-notifier-option + (let [notifier (reify Notifier + (info [_ _]) + (error [_ _]) + (error [_ _ _])) + wiremock (server/init-wiremock {:notifier notifier})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (= notifier (-> options (.notifier))))))) + +(deftest can-set-chunked-transfer-encoding-option + (let [params [[:always (Options$ChunkedEncodingPolicy/ALWAYS)] + [:body-file (Options$ChunkedEncodingPolicy/BODY_FILE)] + [:never (Options$ChunkedEncodingPolicy/NEVER)]]] + + (doseq [[encoding expected-policy] params] + (let [wiremock (server/init-wiremock {:use-chunked-transfer-encoding encoding})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (= expected-policy (-> options (.getChunkedEncodingPolicy))))))))) + +(deftest cannot-set-unknown-wiremock-option + (is (thrown-with-msg? IllegalArgumentException #":unknown is not a recognizable clj-wiremock option." + (server/init-wiremock {:unknown 123})))) + +(deftest cannot-set-unknown-chunked-transfer-encoding-option + (is (thrown-with-msg? IllegalArgumentException #":unknown is not a recognizable chunked encoding policy. \(try :always, :body-file, or :never instead\)" + (server/init-wiremock {:use-chunked-transfer-encoding :unknown})))) + + +(deftest can-set-stub-cors-enabled-option + (let [wiremock (server/init-wiremock {:stub-cors-enabled? true})] + + (let [options (-> (.getOptions (.wmk-java wiremock)))] + (is (= true (-> options (.getStubCorsEnabled))))))) \ No newline at end of file