diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5a20970..7008ad1d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: with: lein: latest - name: Run tests - run: lein do clean, all midje, all check + run: lein do clean, all test, all check deploy: concurrency: deploy needs: test diff --git a/CHANGELOG.md b/CHANGELOG.md index c10479db..7e363a89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ See also: [compojure-api 1.1.x changelog](./CHANGELOG-1.1.x.md) * Add static context optimization coach * `-Dcompojure.api.meta.static-context-coach=print` to print hints * `-Dcompojure.api.meta.static-context-coach=assert` to assert hints +* port unit tests from midje to clojure.test ## 2.0.0-alpha31 (2019-12-20) diff --git a/project.clj b/project.clj index da6f6156..bfc30239 100644 --- a/project.clj +++ b/project.clj @@ -30,14 +30,12 @@ [reloaded.repl "0.2.4"] [com.stuartsierra/component "0.4.0"]]} :dev {:plugins [[lein-clojars "0.9.1"] - [lein-midje "3.2.1"] [lein-ring "0.12.5"] [funcool/codeina "0.5.0"]] :dependencies [[org.clojure/clojure "1.10.1"] [org.clojure/core.async "0.6.532"] [javax.servlet/javax.servlet-api "4.0.1"] [peridot "0.5.2"] - [midje "1.9.9"] [com.rpl/specter "1.1.3"] [com.stuartsierra/component "0.4.0"] [expound "0.8.2"] @@ -90,7 +88,7 @@ :aliases {"all" ["with-profile" "dev:dev,async"] "start-thingie" ["run"] "aot-uberjar" ["with-profile" "uberjar" "do" "clean," "ring" "uberjar"] - "test-ancient" ["midje"] + "test-ancient" ["test"] "perf" ["with-profile" "default,dev,perf"] "deploy!" ^{:doc "Recompile sources, then deploy if tests succeed."} - ["do" ["clean"] ["midje"] ["deploy" "clojars"]]}) + ["do" ["clean"] ["test"] ["deploy" "clojars"]]}) diff --git a/test/compojure/api/coercion/schema_coercion_test.clj b/test/compojure/api/coercion/schema_coercion_test.clj index 6442d3da..cc9c0247 100644 --- a/test/compojure/api/coercion/schema_coercion_test.clj +++ b/test/compojure/api/coercion/schema_coercion_test.clj @@ -1,6 +1,6 @@ (ns compojure.api.coercion.schema-coercion-test - (:require [midje.sweet :refer :all] - [schema.core :as s] + (:require [schema.core :as s] + [clojure.test :refer [deftest is testing]] [compojure.api.sweet :refer :all] [compojure.api.test-utils :refer :all] [compojure.api.request :as request] @@ -10,66 +10,69 @@ [compojure.api.coercion.core :as cc]) (:import (schema.utils ValidationError NamedError))) -(fact "stringify-error" - (fact "ValidationError" - (class (s/check s/Int "foo")) => ValidationError - (cs/stringify (s/check s/Int "foo")) => "(not (integer? \"foo\"))" - (cs/stringify (s/check {:foo s/Int} {:foo "foo"})) => {:foo "(not (integer? \"foo\"))"}) - (fact "NamedError" - (class (s/check (s/named s/Int "name") "foo")) => NamedError - (cs/stringify (s/check (s/named s/Int "name") "foo")) => "(named (not (integer? \"foo\")) \"name\")") - (fact "Schema" - (cs/stringify {:total (s/constrained s/Int pos?)}) => {:total "(constrained Int pos?)"})) +(deftest stringify-error-test + (testing "ValidationError" + (is (= ValidationError (class (s/check s/Int "foo")))) + (is (= "(not (integer? \"foo\"))" (cs/stringify (s/check s/Int "foo")))) + (is (= {:foo "(not (integer? \"foo\"))"} (cs/stringify (s/check {:foo s/Int} {:foo "foo"}))))) + (testing "NamedError" + (is (= NamedError (class (s/check (s/named s/Int "name") "foo")))) + (is (= "(named (not (integer? \"foo\")) \"name\")" (cs/stringify (s/check (s/named s/Int "name") "foo"))))) + (testing "Schema" + (is (= {:total "(constrained Int pos?)"} (cs/stringify {:total (s/constrained s/Int pos?)}))))) -(s/defschema Schema {:kikka s/Keyword}) +(s/defschema Schema2 {:kikka s/Keyword}) (def valid-value {:kikka :kukka}) (def invalid-value {:kikka "kukka"}) -(fact "request-coercion" - (let [c! #(coercion/coerce-request! Schema :body-params :body false false %)] +(deftest request-coercion-test + (let [c! #(coercion/coerce-request! Schema2 :body-params :body false false %)] - (fact "default coercion" - (c! {:body-params valid-value}) => valid-value - (c! {:body-params invalid-value}) => throws + (testing "default coercion" + (is (= valid-value (c! {:body-params valid-value}))) + (is (thrown? Exception (c! {:body-params invalid-value}))) (try (c! {:body-params invalid-value}) (catch Exception e - (ex-data e) => (just {:type :compojure.api.exception/request-validation - :coercion (coercion/resolve-coercion :schema) - :in [:request :body-params] - :schema Schema - :value invalid-value - :errors anything - :request {:body-params {:kikka "kukka"}}})))) - - (fact ":schema coercion" - (c! {:body-params valid-value - ::request/coercion :schema}) => valid-value - (c! {:body-params invalid-value - ::request/coercion :schema}) => throws) - - (fact "format-based coercion" - (c! {:body-params valid-value - :muuntaja/request {:format "application/json"}}) => valid-value - (c! {:body-params invalid-value - :muuntaja/request {:format "application/json"}}) => valid-value) - - (fact "no coercion" - (c! {:body-params valid-value - ::request/coercion nil - :muuntaja/request {:format "application/json"}}) => valid-value - (c! {:body-params invalid-value - ::request/coercion nil - :muuntaja/request {:format "application/json"}}) => invalid-value))) + (is (contains? (ex-data e) :errors)) + (is (= {:type :compojure.api.exception/request-validation + :coercion (coercion/resolve-coercion :schema) + :in [:request :body-params] + :schema Schema2 + :value invalid-value + :request {:body-params {:kikka "kukka"}}} + (select-keys (ex-data e) + [:type :coercion :in :schema :value :request])))))) + + (testing ":schema coercion" + (is (= valid-value (c! {:body-params valid-value + ::request/coercion :schema}))) + (is (thrown? Exception (c! {:body-params invalid-value + ::request/coercion :schema})))) + + (testing "format-based coercion" + (is (= valid-value + (c! {:body-params valid-value + :muuntaja/request {:format "application/json"}}))) + (is (= valid-value + (c! {:body-params invalid-value + :muuntaja/request {:format "application/json"}})))) + + (testing "no coercion" + (is (= valid-value + (c! {:body-params valid-value + ::request/coercion nil + :muuntaja/request {:format "application/json"}}))) + (is (= invalid-value + (c! {:body-params invalid-value + ::request/coercion nil + :muuntaja/request {:format "application/json"}})))))) (defn ok [body] {:status 200, :body body}) -(defn ok? [body] - (contains (ok body))) - -(def responses {200 {:schema Schema}}) +(def responses {200 {:schema Schema2}}) (def custom-coercion (cs/->SchemaCoercion @@ -77,62 +80,81 @@ (-> cs/default-options (assoc-in [:response :formats "application/json"] cs/json-coercion-matcher)))) -(fact "response-coercion" - (let [c! coercion/coerce-response!] +(deftest response-coercion-test + (let [c! coercion/coerce-response! + request {}] - (fact "default coercion" - (c! ..request.. (ok valid-value) responses) => (ok? valid-value) - (c! ..request.. (ok invalid-value) responses) => (throws) + (testing "default coercion" + (is (= (ok valid-value) + (select-keys (c! request (ok valid-value) responses) + [:status :body]))) + (is (thrown? Exception (c! request (ok invalid-value) responses))) (try - (c! ..request.. (ok invalid-value) responses) + (c! request (ok invalid-value) responses) (catch Exception e - (ex-data e) => (contains {:type :compojure.api.exception/response-validation - :coercion (coercion/resolve-coercion :schema) - :in [:response :body] - :schema Schema - :value invalid-value - :errors anything - :request ..request..})))) - - (fact ":schema coercion" - (fact "default coercion" - (c! {::request/coercion :schema} - (ok valid-value) - responses) => (ok? valid-value) - (c! {::request/coercion :schema} - (ok invalid-value) - responses) => throws)) - - (fact "format-based custom coercion" - (fact "request-negotiated response format" - (c! irrelevant - (ok invalid-value) - responses) => throws - (c! {:muuntaja/response {:format "application/json"} - ::request/coercion custom-coercion} - (ok invalid-value) - responses) => (ok? valid-value))) - - (fact "no coercion" - (c! {::request/coercion nil} - (ok valid-value) - responses) => (ok? valid-value) - (c! {::request/coercion nil} - (ok invalid-value) - responses) => (ok? invalid-value)))) + (is (contains? (ex-data e) :errors)) + (is (= {:type :compojure.api.exception/response-validation + :coercion (coercion/resolve-coercion :schema) + :in [:response :body] + :schema Schema2 + :value invalid-value + :request request} + (select-keys (ex-data e) + [:type :coercion :in :schema :value :request])))))) + + (testing ":schema coercion" + (testing "default coercion" + (is (= (ok valid-value) + (select-keys + (c! {::request/coercion :schema} + (ok valid-value) + responses) + [:status :body]))) + (is (thrown? Exception + (c! {::request/coercion :schema} + (ok invalid-value) + responses))))) + + (testing "format-based custom coercion" + (testing "request-negotiated response format" + (is (thrown? Exception + (c! {} + (ok invalid-value) + responses))) + (is (= (ok valid-value) + (select-keys + (c! {:muuntaja/response {:format "application/json"} + ::request/coercion custom-coercion} + (ok invalid-value) + responses) + [:status :body]))))) + + (testing "no coercion" + (is (= (ok valid-value) + (select-keys + (c! {::request/coercion nil} + (ok valid-value) + responses) + [:status :body]))) + (is (= (ok invalid-value) + (select-keys + (c! {::request/coercion nil} + (ok invalid-value) + responses) + [:status :body])))))) (s/defschema X s/Int) (s/defschema Y s/Int) (s/defschema Total (s/constrained s/Int pos? 'positive-int)) -(s/defschema Schema {:x X, :y Y}) +(s/defschema Schema1 {:x X, :y Y}) -(facts "apis" +(deftest apis-test (let [app (api {:swagger {:spec "/swagger.json"} :coercion :schema} (POST "/body" [] - :body [{:keys [x y]} Schema] + :body [{:keys [x y]} Schema1] (ok {:total (+ x y)})) (POST "/body-map" [] @@ -140,7 +162,7 @@ (ok {:total (+ x (or y 0))})) (GET "/query" [] - :query [{:keys [x y]} Schema] + :query [{:keys [x y]} Schema1] (ok {:total (+ x y)})) (GET "/query-params" [] @@ -162,7 +184,7 @@ (context "/resource" [] (resource - {:get {:parameters {:query-params Schema} + {:get {:parameters {:query-params Schema1} :responses {200 {:schema {:total Total}}} :handler (fn [{{:keys [x y]} :query-params}] (ok {:total (+ x y)}))} @@ -171,88 +193,94 @@ :handler (fn [{{:keys [x y]} :body-params}] (ok {:total (+ x (or y 0))}))}})))] - (fact "query" + (testing "query" (let [[status body] (get* app "/query" {:x "1", :y 2})] - status => 200 - body => {:total 3}) + (is (= 200 status)) + (is (= {:total 3} body))) (let [[status body] (get* app "/query" {:x "1", :y "kaks"})] - status => 400 - body => {:coercion "schema" - :in ["request" "query-params"] - :errors {:y "(not (integer? \"kaks\"))"} - :schema "{:x Int, :y Int}" - :type "compojure.api.exception/request-validation" - :value {:x "1", :y "kaks"}})) - - (fact "body" + (is (= 400 status)) + (is (= {:coercion "schema" + :in ["request" "query-params"] + :errors {:y "(not (integer? \"kaks\"))"} + :schema "{:x Int, :y Int}" + :type "compojure.api.exception/request-validation" + :value {:x "1", :y "kaks"}} + body)))) + + (testing "body" (let [[status body] (post* app "/body" (json-string {:x 1, :y 2, #_#_:z 3}))] - status => 200 - body => {:total 3})) + (is (= 200 status)) + (is (= {:total 3} body)))) - (fact "body-map" + (testing "body-map" (let [[status body] (post* app "/body-map" (json-string {:x 1, :y 2}))] - status => 200 - body => {:total 3}) + (is (= 200 status)) + (is (= {:total 3} body))) (let [[status body] (post* app "/body-map" (json-string {:x 1}))] - status => 200 - body => {:total 1})) + (is (= 200 status)) + (is (= {:total 1} body)))) - (fact "body-string" + (testing "body-string" (let [[status body] (post* app "/body-string" (json-string "kikka"))] - status => 200 - body => {:body "kikka"})) + (is (= 200 status)) + (is (= {:body "kikka"} body)))) - (fact "query-params" + (testing "query-params" (let [[status body] (get* app "/query-params" {:x "1", :y 2})] - status => 200 - body => {:total 3}) + (is (= 200 status)) + (is (= {:total 3} body))) (let [[status body] (get* app "/query-params" {:x "1", :y "a"})] - status => 400 - body => (contains {:coercion "schema" - :in ["request" "query-params"]}))) + (is (= 400 status)) + (is (= {:coercion "schema" + :in ["request" "query-params"]} + (select-keys body [:coercion :in]))))) - (fact "body-params" + (testing "body-params" (let [[status body] (post* app "/body-params" (json-string {:x 1, :y 2}))] - status => 200 - body => {:total 3}) + (is (= 200 status)) + (is (= {:total 3} body))) (let [[status body] (post* app "/body-params" (json-string {:x 1}))] - status => 200 - body => {:total 1}) + (is (= 200 status)) + (is (= {:total 1} body))) (let [[status body] (post* app "/body-params" (json-string {:x "1"}))] - status => 400 - body => (contains {:coercion "schema" - :in ["request" "body-params"]}))) + (is (= 400 status)) + (is (= {:coercion "schema" + :in ["request" "body-params"]} + (select-keys body [:coercion :in]))))) - (fact "response" + (testing "response" (let [[status body] (get* app "/response" {:x 1, :y 2})] - status => 200 - body => {:total 3}) + (is (= 200 status)) + (is (= {:total 3} body))) (let [[status body] (get* app "/response" {:x -1, :y -2})] - status => 500 - body => (contains {:coercion "schema" - :in ["response" "body"]}))) + (is (= 500 status)) + (is (= {:coercion "schema" + :in ["response" "body"]} + (select-keys body [:coercion :in]))))) - (fact "resource" - (fact "parameters as specs" + (testing "resource" + (testing "parameters as specs" (let [[status body] (get* app "/resource" {:x 1, :y 2})] - status => 200 - body => {:total 3}) + (is (= 200 status)) + (is (= {:total 3} body))) (let [[status body] (get* app "/resource" {:x -1, :y -2})] - status => 500 - body => (contains {:coercion "schema" - :in ["response" "body"]}))) + (is (= 500 status)) + (is (= {:coercion "schema" + :in ["response" "body"]} + (select-keys body [:coercion :in]))))) - (fact "parameters as data-specs" + (testing "parameters as data-specs" (let [[status body] (post* app "/resource" (json-string {:x 1, :y 2}))] - status => 200 - body => {:total 3}) + (is (= 200 status)) + (is (= {:total 3} body))) (let [[status body] (post* app "/resource" (json-string {:x 1}))] - status => 200 - body => {:total 1}) + (is (= 200 status)) + (is (= {:total 1} body))) (let [[status body] (post* app "/resource" (json-string {:x -1, :y -2}))] - status => 500 - body => (contains {:coercion "schema" - :in ["response" "body"]})))) + (is (= 500 status)) + (is (= {:coercion "schema" + :in ["response" "body"]} + (select-keys body [:coercion :in])))))) - (fact "generates valid swagger spec" - (validator/validate app) =not=> (throws)))) + (testing "generates valid swagger spec" + (is (validator/validate app))))) diff --git a/test/compojure/api/coercion_test.clj b/test/compojure/api/coercion_test.clj index f12e08e4..811a91fd 100644 --- a/test/compojure/api/coercion_test.clj +++ b/test/compojure/api/coercion_test.clj @@ -1,23 +1,22 @@ (ns compojure.api.coercion-test (:require [compojure.api.sweet :refer :all] [compojure.api.test-utils :refer :all] - [midje.sweet :refer :all] + [clojure.test :refer [deftest]] + [clojure.test :refer [deftest testing is]] [ring.util.http-response :refer :all] [clojure.core.async :as a] [schema.core :as s] [compojure.api.coercion.schema :as cs])) -(defn has-body [expected] - (fn [value] - (= (second value) expected))) +(defn is-has-body [expected value] + (is (= (second value) expected))) -(defn fails-with [expected-status] - (fn [[status body]] - (and (= status expected-status) - (every? (partial contains? body) [:type :coercion :in :value :schema :errors])))) +(defn is-fails-with [expected-status [status body]] + (is (= status expected-status)) + (is (every? (partial contains? body) [:type :coercion :in :value :schema :errors]))) -(facts "schema coercion" - (fact "response schemas" +(deftest schema-coercion-test + (testing "response schemas" (let [r-200 (GET "/" [] :query-params [{value :- s/Int nil}] :responses {200 {:schema {:value s/Str}}} @@ -31,133 +30,133 @@ :responses {200 {:schema {:value s/Str}} :default {:schema {:value s/Int}}} (ok {:value (or value "123")}))] - (fact "200" - (get* (api r-200) "/") => (has-body {:value "123"}) - (get* (api r-200) "/" {:value 123}) => (fails-with 500)) - - (fact "exception data" - (get* (api r-200) "/" {:value 123}) - => (contains - [500 - {:type "compojure.api.exception/response-validation" - :coercion "schema", - :in ["response" "body"], - :value {:value 123}, - :schema "{:value java.lang.String}", - :errors {:value "(not (instance? java.lang.String 123))"}}])) - - (fact ":default" - (get* (api r-default) "/") => (has-body {:value "123"}) - (get* (api r-default) "/" {:value 123}) => (fails-with 500)) - - (fact ":default" - (get* (api r-200-default) "/") => (has-body {:value "123"}) - (get* (api r-200-default) "/" {:value 123}) => (fails-with 500)))) - - (fact "custom coercion" - - (fact "response coercion" + (testing "200" + (is-has-body {:value "123"} (get* (api r-200) "/")) + (is-fails-with 500 (get* (api r-200) "/" {:value 123}))) + + (testing "exception data" + (let [ex (get* (api r-200) "/" {:value 123})] + (is (= 500 (first ex))) + (is (= {:type "compojure.api.exception/response-validation" + :coercion "schema", + :in ["response" "body"], + :value {:value 123}, + :schema "{:value java.lang.String}", + :errors {:value "(not (instance? java.lang.String 123))"}} + (select-keys (second ex) [:type :coercion :in :value :schema :errors]))))) + + (testing ":default" + (is-has-body {:value "123"} (get* (api r-default) "/")) + (is-fails-with 500 (get* (api r-default) "/" {:value 123}))) + + (testing ":default" + (is-has-body {:value "123"} (get* (api r-200-default) "/")) + (is-fails-with 500 (get* (api r-200-default) "/" {:value 123}))))) + + (testing "custom coercion" + + (testing "response coercion" (let [ping-route (GET "/ping" [] :return {:pong s/Str} (ok {:pong 123}))] - (fact "by default, applies response coercion" + (testing "by default, applies response coercion" (let [app (api ping-route)] - (get* app "/ping") => (fails-with 500))) + (is-fails-with 500 (get* app "/ping")))) - (fact "response-coercion can be disabled" - (fact "separately" + (testing "response-coercion can be disabled" + (testing "separately" (let [app (api {:coercion (cs/create-coercion (dissoc cs/default-options :response))} ping-route)] (let [[status body] (get* app "/ping")] - status => 200 - body => {:pong 123}))) - (fact "all coercion" + (is (= 200 status)) + (is (= {:pong 123} body))))) + (testing "all coercion" (let [app (api {:coercion nil} ping-route)] (let [[status body] (get* app "/ping")] - status => 200 - body => {:pong 123})))) + (is (= 200 status)) + (is (= {:pong 123} body)))))) - (fact "coercion for async handlers" + (testing "coercion for async handlers" (binding [*async?* true] - (fact "successful" + (testing "successful" (let [app (api (GET "/async" [] :return s/Str (a/go (ok "abc"))))] - (get* app "/async") => (has-body "abc"))) - (fact "failing" + (is-has-body "abc" (get* app "/async")))) + (testing "failing" (let [app (api (GET "/async" [] :return s/Int (a/go (ok "foo"))))] - (get* app "/async") => (fails-with 500))))))) + (is-fails-with 500 (get* app "/async")))))))) - (fact "body coersion" + (testing "body coersion" (let [beer-route (POST "/beer" [] :body [body {:beers #{(s/enum "ipa" "apa")}}] (ok body))] - (fact "by default, applies body coercion (to set)" + (testing "by default, applies body coercion (to set)" (let [app (api beer-route)] (let [[status body] (post* app "/beer" (json-string {:beers ["ipa" "apa" "ipa"]}))] - status => 200 - body => {:beers ["ipa" "apa"]}))) + (is (= 200 status)) + (is (= {:beers ["ipa" "apa"]} body))))) - (fact "body-coercion can be disabled" + (testing "body-coercion can be disabled" (let [no-body-coercion (cs/create-coercion (dissoc cs/default-options :body)) app (api {:coercion no-body-coercion} beer-route)] (let [[status body] (post* app "/beer" (json-string {:beers ["ipa" "apa" "ipa"]}))] - status => 200 - body => {:beers ["ipa" "apa" "ipa"]})) + (is (= 200 status)) + (is (= {:beers ["ipa" "apa" "ipa"]} body)))) (let [app (api {:coercion nil} beer-route)] (let [[status body] (post* app "/beer" (json-string {:beers ["ipa" "apa" "ipa"]}))] - status => 200 - body => {:beers ["ipa" "apa" "ipa"]}))) + (is (= 200 status)) + (is (= {:beers ["ipa" "apa" "ipa"]} body))))) - (fact "body-coercion can be changed" + (testing "body-coercion can be changed" (let [nop-body-coercion (cs/create-coercion (assoc cs/default-options :body {:default (constantly nil)})) app (api {:coercion nop-body-coercion} beer-route)] - (post* app "/beer" (json-string {:beers ["ipa" "apa" "ipa"]})) => (fails-with 400))))) + (is-fails-with 400 (post* app "/beer" (json-string {:beers ["ipa" "apa" "ipa"]}))))))) - (fact "query coercion" + (testing "query coercion" (let [query-route (GET "/query" [] :query-params [i :- s/Int] (ok {:i i}))] - (fact "by default, applies query coercion (string->int)" + (testing "by default, applies query coercion (string->int)" (let [app (api query-route)] (let [[status body] (get* app "/query" {:i 10})] - status => 200 - body => {:i 10}))) + (is (= 200 status)) + (is (= {:i 10} body))))) - (fact "query-coercion can be disabled" + (testing "query-coercion can be disabled" (let [no-query-coercion (cs/create-coercion (dissoc cs/default-options :string)) app (api {:coercion no-query-coercion} query-route)] (let [[status body] (get* app "/query" {:i 10})] - status => 200 - body => {:i "10"}))) + (is (= 200 status)) + (is (= {:i "10"} body))))) - (fact "query-coercion can be changed" + (testing "query-coercion can be changed" (let [nop-query-coercion (cs/create-coercion (assoc cs/default-options :string {:default (constantly nil)})) app (api {:coercion nop-query-coercion} query-route)] - (get* app "/query" {:i 10}) => (fails-with 400))))) + (is-fails-with 400 (get* app "/query" {:i 10})))))) - (fact "route-specific coercion" + (testing "route-specific coercion" (let [app (api (GET "/default" [] :query-params [i :- s/Int] @@ -171,62 +170,62 @@ :query-params [i :- s/Int] (ok {:i i})))] - (fact "default coercion" + (testing "default coercion" (let [[status body] (get* app "/default" {:i 10})] - status => 200 - body => {:i 10})) - - (fact "disabled coercion" - (get* app "/disabled-coercion" {:i 10}) => (fails-with 400)) - - (fact "exception data" - (get* app "/disabled-coercion" {:i 10}) - => (contains - [400 - (contains - {:type "compojure.api.exception/request-validation" - :coercion "schema", - :in ["request" "query-params"], - :value {:i "10"} - :schema "{Keyword Any, :i Int}", - :errors {:i "(not (integer? \"10\"))"}})])) - - (fact "no coercion" + (is (= 200 status)) + (is (= {:i 10} body)))) + + (testing "disabled coercion" + (is-fails-with 400 (get* app "/disabled-coercion" {:i 10}))) + + (testing "exception data" + (let [ex (get* app "/disabled-coercion" {:i 10})] + (is (= 400 (first ex))) + (is (= {:type "compojure.api.exception/request-validation" + :coercion "schema", + :in ["request" "query-params"], + :value {:i "10"} + :schema "{Keyword Any, :i Int}", + :errors {:i "(not (integer? \"10\"))"}} + (select-keys (second ex) + [:type :coercion :in :value :schema :errors]))))) + + (testing "no coercion" (let [[status body] (get* app "/no-coercion" {:i 10})] - status => 200 - body => {:i "10"}))))) + (is (= 200 status)) + (is (= {:i "10"} body))))))) - (facts "apiless coercion" + (testing "apiless coercion" - (fact "use default-coercion-matchers by default" + (testing "use default-coercion-matchers by default" (let [app (context "/api" [] :query-params [{y :- Long 0}] (GET "/ping" [] :query-params [x :- Long] (ok [x y])))] - (app {:request-method :get :uri "/api/ping" :query-params {}}) => (throws) - (app {:request-method :get :uri "/api/ping" :query-params {:x "abba"}}) => (throws) - (app {:request-method :get :uri "/api/ping" :query-params {:x "1"}}) => (contains {:body [1 0]}) - (app {:request-method :get :uri "/api/ping" :query-params {:x "1", :y 2}}) => (contains {:body [1 2]}) - (app {:request-method :get :uri "/api/ping" :query-params {:x "1", :y "abba"}}) => (throws))) + (is (thrown? Exception (app {:request-method :get :uri "/api/ping" :query-params {}}))) + (is (thrown? Exception (app {:request-method :get :uri "/api/ping" :query-params {:x "abba"}}))) + (is (= [1 0] (:body (app {:request-method :get :uri "/api/ping" :query-params {:x "1"}})))) + (is (= [1 2] (:body (app {:request-method :get :uri "/api/ping" :query-params {:x "1", :y 2}})))) + (is (thrown? Exception (app {:request-method :get :uri "/api/ping" :query-params {:x "1", :y "abba"}}))))) - (fact "coercion can be overridden" + (testing "coercion can be overridden" (let [app (context "/api" [] :query-params [{y :- Long 0}] (GET "/ping" [] :coercion nil :query-params [x :- Long] (ok [x y])))] - (app {:request-method :get :uri "/api/ping" :query-params {}}) => (throws) - (app {:request-method :get :uri "/api/ping" :query-params {:x "abba"}}) => (contains {:body ["abba" 0]}) - (app {:request-method :get :uri "/api/ping" :query-params {:x "1"}}) => (contains {:body ["1" 0]}) - (app {:request-method :get :uri "/api/ping" :query-params {:x "1", :y 2}}) => (contains {:body ["1" 2]}) - (app {:request-method :get :uri "/api/ping" :query-params {:x "1", :y "abba"}}) => (throws))) + (is (thrown? Exception (app {:request-method :get :uri "/api/ping" :query-params {}}))) + (is (= ["abba" 0] (:body (app {:request-method :get :uri "/api/ping" :query-params {:x "abba"}})))) + (is (= ["1" 0] (:body (app {:request-method :get :uri "/api/ping" :query-params {:x "1"}})))) + (is (= ["1" 2] (:body (app {:request-method :get :uri "/api/ping" :query-params {:x "1", :y 2}})))) + (is (thrown? Exception (app {:request-method :get :uri "/api/ping" :query-params {:x "1", :y "abba"}}))))) - (fact "context coercion is used for subroutes" + (testing "context coercion is used for subroutes" (let [app (context "/api" [] :coercion nil (GET "/ping" [] :query-params [x :- Long] (ok x)))] - (app {:request-method :get :uri "/api/ping" :query-params {:x "abba"}}) => (contains {:body "abba"}))))) + (is (= "abba" (:body (app {:request-method :get :uri "/api/ping" :query-params {:x "abba"}})))))))) diff --git a/test/compojure/api/common_test.clj b/test/compojure/api/common_test.clj index 54d39ea6..58a5de28 100644 --- a/test/compojure/api/common_test.clj +++ b/test/compojure/api/common_test.clj @@ -1,38 +1,38 @@ (ns compojure.api.common-test (:require [compojure.api.common :as common] - [midje.sweet :refer :all] + [clojure.test :refer [deftest testing is]] [criterium.core :as cc])) -(fact "group-with" - (common/group-with pos? [1 -10 2 -4 -1 999]) => [[1 2 999] [-10 -4 -1]] - (common/group-with pos? [1 2 999]) => [[1 2 999] nil]) +(deftest group-with-test + (is (= (common/group-with pos? [1 -10 2 -4 -1 999]) [[1 2 999] [-10 -4 -1]])) + (is (= (common/group-with pos? [1 2 999]) [[1 2 999] nil]))) -(fact "extract-parameters" +(deftest extract-parameters-test - (facts "expect body" - (common/extract-parameters [] true) => [{} nil] - (common/extract-parameters [{:a 1}] true) => [{} [{:a 1}]] - (common/extract-parameters [:a 1] true) => [{:a 1} nil] - (common/extract-parameters [{:a 1} {:b 2}] true) => [{:a 1} [{:b 2}]] - (common/extract-parameters [:a 1 {:b 2}] true) => [{:a 1} [{:b 2}]]) + (testing "expect body" + (is (= (common/extract-parameters [] true) [{} nil])) + (is (= (common/extract-parameters [{:a 1}] true) [{} [{:a 1}]])) + (is (= (common/extract-parameters [:a 1] true) [{:a 1} nil])) + (is (= (common/extract-parameters [{:a 1} {:b 2}] true) [{:a 1} [{:b 2}]])) + (is (= (common/extract-parameters [:a 1 {:b 2}] true) [{:a 1} [{:b 2}]]))) - (facts "don't expect body" - (common/extract-parameters [] false) => [{} nil] - (common/extract-parameters [{:a 1}] false) => [{:a 1} nil] - (common/extract-parameters [:a 1] false) => [{:a 1} nil] - (common/extract-parameters [{:a 1} {:b 2}] false) => [{:a 1} [{:b 2}]] - (common/extract-parameters [:a 1 {:b 2}] false) => [{:a 1} [{:b 2}]])) + (testing "don't expect body" + (is (= (common/extract-parameters [] false) [{} nil])) + (is (= (common/extract-parameters [{:a 1}] false) [{:a 1} nil])) + (is (= (common/extract-parameters [:a 1] false) [{:a 1} nil])) + (is (= (common/extract-parameters [{:a 1} {:b 2}] false) [{:a 1} [{:b 2}]])) + (is (= (common/extract-parameters [:a 1 {:b 2}] false) [{:a 1} [{:b 2}]])))) -(fact "merge-vector" - (common/merge-vector nil) => nil - (common/merge-vector [{:a 1}]) => {:a 1} - (common/merge-vector [{:a 1} {:b 2}]) => {:a 1 :b 2}) +(deftest merge-vector-test + (is (= (common/merge-vector nil) nil)) + (is (= (common/merge-vector [{:a 1}]) {:a 1})) + (is (= (common/merge-vector [{:a 1} {:b 2}]) {:a 1 :b 2}))) -(fact "fast-merge-map" +(deftest fast-merge-map-test (let [x {:a 1, :b 2, :c 3} y {:a 2, :d 4, :e 5}] - (common/fast-map-merge x y) => {:a 2, :b 2, :c 3 :d 4, :e 5} - (common/fast-map-merge x y) => (merge x y))) + (is (= (common/fast-map-merge x y) {:a 2, :b 2, :c 3 :d 4, :e 5})) + (is (= (common/fast-map-merge x y) (merge x y))))) (comment (require '[criterium.core :as cc]) diff --git a/test/compojure/api/help_test.clj b/test/compojure/api/help_test.clj index 85abfb7f..b7e72a1f 100644 --- a/test/compojure/api/help_test.clj +++ b/test/compojure/api/help_test.clj @@ -1,14 +1,14 @@ (ns compojure.api.help-test (:require [compojure.api.help :as help] [compojure.api.meta :as meta] - [midje.sweet :refer :all])) + [clojure.test :refer [deftest is testing]])) -(facts "help-for-api-meta" - (fact "all restructure-param methods have a help text" +(deftest help-for-api-meta-test + (testing "all restructure-param methods have a help text" (let [restructure-method-names (-> meta/restructure-param methods keys) meta-help-topics (-> (methods help/help-for) (dissoc ::help/default) keys (->> (filter #(= :meta (first %))) (map second)))] - (set restructure-method-names) => (set meta-help-topics)))) + (is (= (set restructure-method-names) (set meta-help-topics)))))) diff --git a/test/compojure/api/integration_test.clj b/test/compojure/api/integration_test.clj index 3cfe35d6..037bc46e 100644 --- a/test/compojure/api/integration_test.clj +++ b/test/compojure/api/integration_test.clj @@ -1,9 +1,11 @@ (ns compojure.api.integration-test (:require [compojure.api.sweet :refer :all] + [clojure.string :as str] + [clojure.test :refer [deftest is testing]] + [compojure.api.test-domain :refer [Pizza burger-routes]] [compojure.api.test-utils :refer :all] [compojure.api.exception :as ex] [compojure.api.swagger :as swagger] - [midje.sweet :refer :all] [ring.util.http-response :refer :all] [ring.util.http-predicates :as http] [schema.core :as s] @@ -45,6 +47,9 @@ (def mw* "mw") +(defn is-200-status [status] + (is (= 200 status))) + (defn middleware* "This middleware appends given value or 1 to a header in request and response." ([handler] (middleware* handler 1)) @@ -101,26 +106,26 @@ ;; Facts ;; -(facts "core routes" +(deftest core-routes-test - (fact "keyword options" + (testing "keyword options" (let [route (GET "/ping" [] :return String (ok "kikka"))] - (route {:request-method :get :uri "/ping"}) => (contains {:body "kikka"}))) + (is (= "kikka" (:body (route {:request-method :get :uri "/ping"})))))) - (fact "map options" + (testing "map options" (let [route (GET "/ping" [] {:return String} (ok "kikka"))] - (route {:request-method :get :uri "/ping"}) => (contains {:body "kikka"}))) + (is (= "kikka" (:body (route {:request-method :get :uri "/ping"})))))) - (fact "map return" + (testing "map return" (let [route (GET "/ping" [] {:body "kikka"})] - (route {:request-method :get :uri "/ping"}) => (contains {:body "kikka"})))) + (is (= "kikka" (:body (route {:request-method :get :uri "/ping"}))))))) -(facts "middleware ordering" +(deftest middleware-ordering-test (let [app (api {:middleware [[middleware* 0]]} (route-middleware [[middleware* "a"] [middleware* "b"]] @@ -133,22 +138,22 @@ :middleware [(fn [handler] (middleware* handler "e")) [middleware* "f"]] (reply-mw* req))))))] - (fact "are applied left-to-right" + (testing "are applied left-to-right" (let [[status _ headers] (get* app "/middlewares/simple" {})] - status => 200 - (get headers mw*) => "012ab/ba210")) + (is (= 200 status)) + (is (= "012ab/ba210" (get headers mw*))))) - (fact "are applied left-to-right closest one first" + (testing "are applied left-to-right closest one first" (let [[status _ headers] (get* app "/middlewares/nested" {})] - status => 200 - (get headers mw*) => "012abcd/dcba210")) + (is (= 200 status)) + (is (= "012abcd/dcba210" (get headers mw*))))) - (fact "are applied left-to-right for both nested & declared closest one first" + (testing "are applied left-to-right for both nested & declared closest one first" (let [[status _ headers] (get* app "/middlewares/nested-declared" {})] - status => 200 - (get headers mw*) => "012abcdef/fedcba210")))) + (is (= 200 status)) + (is (= "012abcdef/fedcba210" (get headers mw*))))))) -(facts "context middleware" +(deftest context-middleware-test (let [app (api (context "/middlewares" [] :middleware [(fn [h] (fn mw @@ -156,12 +161,12 @@ ([r respond _] (respond (mw r)))))] (GET "/simple" req (reply-mw* req))))] - (fact "is applied even if route is not matched" + (testing "is applied even if route is not matched" (let [[status body] (get* app "/middlewares/non-existing" {})] - status => 200 - body => {:middleware "hello"})))) + (is (= 200 status)) + (is (= {:middleware "hello"} body)))))) -(facts "middleware - multiple routes" +(deftest middleware-multiple-routes-test (let [app (api (GET "/first" [] (ok {:value "first"})) @@ -170,31 +175,31 @@ (ok {:value "second"})) (GET "/third" [] (ok {:value "third"})))] - (fact "first returns first" + (testing "first returns first" (let [[status body] (get* app "/first" {})] - status => 200 - body => {:value "first"})) - (fact "second returns foo" + (is (= 200 status)) + (is (= {:value "first"} body)))) + (testing "second returns foo" (let [[status body] (get* app "/second" {})] - status => 200 - body => {:value "foo"})) - (fact "third returns third" + (is-200-status status) + (is (= {:value "foo"} body)))) + (testing "third returns third" (let [[status body] (get* app "/third" {})] - status => 200 - body => {:value "third"})))) + (is-200-status status) + (is (= {:value "third"} body)))))) -(facts "middleware - editing request" +(deftest middleware-editing-request-test (let [app (api (GET "/first" [] :query-params [x :- Long] :middleware [middleware-x] (ok {:value x})))] - (fact "middleware edits the parameter before route body" + (testing "middleware edits the parameter before route body" (let [[status body] (get* app "/first?x=5" {})] - status => 200 - body => {:value 10})))) + (is-200-status status) + (is (= {:value 10} body)))))) -(fact ":body, :query, :headers and :return" +(deftest body-query-headers-and-return-test (let [app (api (context "/models" [] (GET "/pertti" [] @@ -229,60 +234,59 @@ :return User (ok user))))] - (fact "GET" + (testing "GET" (let [[status body] (get* app "/models/pertti")] - status => 200 - body => pertti)) + (is-200-status status) + (is (= pertti body)))) - (fact "GET with smart destructuring" + (testing "GET with smart destructuring" (let [[status body] (get* app "/models/user" pertti)] - status => 200 - body => pertti)) + (is-200-status status) + (is (= pertti body)))) - (fact "POST with smart destructuring" + (testing "POST with smart destructuring" (let [[status body] (post* app "/models/user" (json-string pertti))] - status => 200 - body => pertti)) + (is-200-status status) + (is (= pertti body)))) - (fact "POST with smart destructuring - lists" + (testing "POST with smart destructuring - lists" (let [[status body] (post* app "/models/user_list" (json-string [pertti]))] - status => 200 - body => [pertti])) + (is-200-status status) + (is (= [pertti] body)))) - (fact "POST with smart destructuring - sets" + (testing "POST with smart destructuring - sets" (let [[status body] (post* app "/models/user_set" (json-string #{pertti}))] - status => 200 - body => [pertti])) + (is-200-status status) + (is (= [pertti] body)))) - (fact "POST with compojure destructuring" + (testing "POST with compojure destructuring" (let [[status body] (post* app "/models/user_legacy" (json-string pertti))] - status => 200 - body => pertti)) + (is-200-status status) + (is (= pertti body)))) - (fact "POST with smart destructuring - headers" + (testing "POST with smart destructuring - headers" (let [[status body] (headers-post* app "/models/user_headers" pertti)] - status => 200 - body => pertti)) + (is-200-status status) + (is (= pertti body)))) - (fact "Validation of returned data" + (testing "Validation of returned data" (let [[status] (get* app "/models/invalid-user")] - status => 500)) + (is (= 500 status)))) - (fact "Routes without a :return parameter aren't validated" + (testing "Routes without a :return parameter aren't validated" (let [[status body] (get* app "/models/not-validated")] - status => 200 - body => invalid-user)) + (is-200-status status) + (is (= invalid-user body)))) - (fact "Invalid json in body causes 400 with error message in json" + (testing "Invalid json in body causes 400 with error message in json" (let [[status body] (post* app "/models/user" "{INVALID}")] - status => 400 - body => (contains - {:type "compojure.api.exception/request-parsing" - :message (contains "Malformed application/json") - :original (contains "Unexpected character")}))))) - -(fact ":responses" - (fact "normal cases" + (is (= 400 status)) + (is (= "compojure.api.exception/request-parsing" (:type body))) + (is (str/starts-with? (:message body) "Malformed application/json")) + (is (str/starts-with? (:original body) "Unexpected character")))))) + +(deftest responses-test + (testing "normal cases" (let [app (api (swagger-routes) (GET "/lotto/:x" [] @@ -297,35 +301,35 @@ 4 (forbidden [1]) (not-found {:message "not-found"}))))] - (fact "return case" + (testing "return case" (let [[status body] (get* app "/lotto/1")] - status => 200 - body => [1])) + (is-200-status status) + (is (= [1] body)))) - (fact "return case, non-matching model" + (testing "return case, non-matching model" (let [[status body] (get* app "/lotto/2")] - status => 500 - body => (contains {:errors vector?}))) + (is (= 500 status)) + (is (vector? (:errors body))))) - (fact "error case" + (testing "error case" (let [[status body] (get* app "/lotto/3")] - status => 403 - body => ["error"])) + (is (= 403 status)) + (is (= ["error"] body)))) - (fact "error case, non-matching model" + (testing "error case, non-matching model" (let [[status body] (get* app "/lotto/4")] - status => 500 - body => (contains {:errors vector?}))) + (is (= 500 status)) + (is (-> body :errors vector?)))) - (fact "returning non-predefined http-status code works" + (testing "returning non-predefined http-status code works" (let [[status body] (get* app "/lotto/5")] - body => {:message "not-found"} - status => 404)) + (is (= {:message "not-found"} body)) + (is (= 404 status)))) - (fact "swagger-docs for multiple returns" + (testing "swagger-docs for multiple returns" (-> app get-spec :paths vals first :get :responses keys set)))) - (fact ":responses 200 and :return" + (testing ":responses 200 and :return" (let [app (api (GET "/lotto/:x" [] :path-params [x :- Long] @@ -335,18 +339,19 @@ 1 (ok {:return "ok"}) 2 (ok {:value "ok"}))))] - (fact "return case" + (testing "return case" (let [[status body] (get* app "/lotto/1")] - status => 500 - body => (contains {:errors {:return "disallowed-key" - :value "missing-required-key"}}))) + (is (= 500 status)) + (is (= {:return "disallowed-key" + :value "missing-required-key"} + (:errors body))))) - (fact "return case" + (testing "return case" (let [[status body] (get* app "/lotto/2")] - status => 200 - body => {:value "ok"})))) + (is-200-status status) + (is (= {:value "ok"} body)))))) - (fact ":responses 200 and :return - other way around" + (testing ":responses 200 and :return - other way around" (let [app (api (GET "/lotto/:x" [] :path-params [x :- Long] @@ -356,18 +361,19 @@ 1 (ok {:return "ok"}) 2 (ok {:value "ok"}))))] - (fact "return case" + (testing "return case" (let [[status body] (get* app "/lotto/1")] - status => 200 - body => {:return "ok"})) + (is-200-status status) + (is (= {:return "ok"} body)))) - (fact "return case" + (testing "return case" (let [[status body] (get* app "/lotto/2")] - status => 500 - body => (contains {:errors {:return "missing-required-key" - :value "disallowed-key"}})))))) + (is (= 500 status)) + (is (= {:return "missing-required-key" + :value "disallowed-key"} + (:errors body)))))))) -(fact ":query-params, :path-params, :header-params , :body-params and :form-params" +(deftest query-params-path-params-header-params-body-params-and-form-params-test (let [app (api (context "/smart" [] (GET "/plus" [] @@ -386,37 +392,37 @@ :form-params [x :- Long y :- Long] (ok {:total (/ x y)}))))] - (fact "query-parameters" + (testing "query-parameters" (let [[status body] (get* app "/smart/plus" {:x 2 :y 3})] - status => 200 - body => {:total 5})) + (is-200-status status) + (is (= {:total 5} body)))) - (fact "path-parameters" + (testing "path-parameters" (let [[status body] (get* app "/smart/multiply/2/3")] - status => 200 - body => {:total 6})) + (is-200-status status) + (is (= {:total 6} body)))) - (fact "header-parameters" + (testing "header-parameters" (let [[status body] (get* app "/smart/power" {} {:x 2 :y 3})] - status => 200 - body => {:total 8})) + (is-200-status status) + (is (= {:total 8} body)))) - (fact "form-parameters" + (testing "form-parameters" (let [[status body] (form-post* app "/smart/divide" {:x 6 :y 3})] - status => 200 - body => {:total 2})) + (is-200-status status) + (is (= {:total 2} body)))) - (fact "body-parameters" + (testing "body-parameters" (let [[status body] (post* app "/smart/minus" (json-string {:x 2 :y 3}))] - status => 200 - body => {:total -1})) + (is-200-status status) + (is (= {:total -1} body)))) - (fact "default parameters" + (testing "default parameters" (let [[status body] (post* app "/smart/minus" (json-string {:x 2}))] - status => 200 - body => {:total 1})))) + (is-200-status status) + (is (= {:total 1} body)))))) -(fact "primitive support" +(deftest primitive-support-test (let [app (api {:swagger {:spec "/swagger.json"}} (context "/primitives" [] @@ -433,46 +439,46 @@ :body [longs [Long]] (ok longs))))] - (fact "when :return is set, longs can be returned" + (testing "when :return is set, longs can be returned" (let [[status body] (raw-get* app "/primitives/return-long")] - status => 200 - body => "1")) + (is-200-status status) + (is (= "1" body)))) - (fact "when :return is not set, longs won't be encoded" + (testing "when :return is not set, longs won't be encoded" (let [[status body] (raw-get* app "/primitives/long")] - status => 200 - body => number?)) + (is-200-status status) + (is (number? body)))) - (fact "when :return is set, raw strings can be returned" + (testing "when :return is set, raw strings can be returned" (let [[status body] (raw-get* app "/primitives/return-string")] - status => 200 - body => "\"kikka\"")) + (is-200-status status) + (is (= "\"kikka\"" body)))) - (fact "primitive arrays work" + (testing "primitive arrays work" (let [[status body] (raw-post* app "/primitives/arrays" (json-string [1 2 3]))] - status => 200 - body => "[1,2,3]")) + (is-200-status status) + (is (= "[1,2,3]" body)))) - (fact "swagger-spec is valid" + (testing "swagger-spec is valid" (validator/validate app)) - (fact "primitive array swagger-docs are good" + (testing "primitive array swagger-docs are good" - (-> app get-spec :paths (get "/primitives/arrays") :post :parameters) - => [{:description "" - :in "body" - :name "" - :required true - :schema {:items {:format "int64" - :type "integer"} - :type "array"}}] + (is (= [{:description "" + :in "body" + :name "" + :required true + :schema {:items {:format "int64" + :type "integer"} + :type "array"}}] + (-> app get-spec :paths (get "/primitives/arrays") :post :parameters))) - (-> app get-spec :paths (get "/primitives/arrays") :post :responses :200 :schema) - => {:items {:format "int64", - :type "integer"}, - :type "array"}))) + (is (= {:items {:format "int64", + :type "integer"}, + :type "array"} + (-> app get-spec :paths (get "/primitives/arrays") :post :responses :200 :schema)))))) -(fact "compojure destructuring support" +(deftest compojure-destructuring-support-test (let [app (api (context "/destructuring" [] (GET "/regular" {{:keys [a]} :params} @@ -496,12 +502,12 @@ :b b}))))] (doseq [uri ["regular" "regular2" "vector" "vector2" "symbol" "integrated"]] - (fact {:midje/description uri} + (testing uri (let [[status body] (get* app (str "/destructuring/" uri) {:a "a" :b "b"})] - status => 200 - body => {:a "a" :b "b"}))))) + (is-200-status status) + (is (= {:a "a" :b "b"} body))))))) -(fact "counting execution times, issue #19" +(deftest counting-execution-times-issue-19-test (let [execution-times (atom 0) app (api (GET "/user" [] @@ -510,14 +516,14 @@ (swap! execution-times inc) (ok user)))] - (fact "body is executed one" - @execution-times => 0 + (testing "body is executed one" + (is (zero? @execution-times)) (let [[status body] (get* app "/user" pertti)] - status => 200 - body => pertti) - @execution-times => 1))) + (is-200-status status) + (is (= pertti body))) + (is (= 1 @execution-times))))) -(fact "swagger-docs" +(deftest swagger-docs-test (let [app (api {:formats (m/select-formats m/default-options @@ -526,102 +532,100 @@ (GET "/user" [] (continue)))] - (fact "api-listing shows produces & consumes for known types" - (get-spec app) => {:swagger "2.0" - :info {:title "Swagger API" - :version "0.0.1"} - :basePath "/" - :consumes ["application/json" "application/edn"] - :produces ["application/json" "application/edn"] - :definitions {} - :paths {"/user" {:get {:responses {:default {:description ""}}}}}})) + (testing "api-listing shows produces & consumes for known types" + (is (= {:swagger "2.0" + :info {:title "Swagger API" + :version "0.0.1"} + :basePath "/" + :consumes ["application/json" "application/edn"] + :produces ["application/json" "application/edn"] + :definitions {} + :paths {"/user" {:get {:responses {:default {:description ""}}}}}} + (get-spec app))))) - (fact "swagger-routes" + (testing "swagger-routes" - (fact "with defaults" + (testing "with defaults" (let [app (api (swagger-routes))] - (fact "api-docs are mounted to /" + (testing "api-docs are mounted to /" (let [[status body] (raw-get* app "/")] - status => 200 - body => #"<title>Swagger UI</title>")) + (is-200-status status) + (is (str/includes? body "<title>Swagger UI</title>")))) - (fact "spec is mounted to /swagger.json" + (testing "spec is mounted to /swagger.json" (let [[status body] (get* app "/swagger.json")] - status => 200 - body => (contains {:swagger "2.0"}))))) + (is-200-status status) + (is (= "2.0" (:swagger body))))))) - (fact "with partial overridden values" + (testing "with partial overridden values" (let [app (api (swagger-routes {:ui "/api-docs" :data {:info {:title "Kikka"} :paths {"/ping" {:get {}}}}}))] - (fact "api-docs are mounted" + (testing "api-docs are mounted" (let [[status body] (raw-get* app "/api-docs")] - status => 200 - body => #"<title>Swagger UI</title>")) + (is-200-status status) + (is (str/includes? body "<title>Swagger UI</title>")))) - (fact "spec is mounted to /swagger.json" + (testing "spec is mounted to /swagger.json" (let [[status body] (get* app "/swagger.json")] - status => 200 - body => (contains - {:swagger "2.0" - :info (contains - {:title "Kikka"}) - :paths (contains - {(keyword "/ping") anything})})))))) + (is-200-status status) + (is (= "2.0" (:swagger body))) + (is (= "Kikka" (-> body :info :title))) + (is (some? (-> body :paths (get (keyword "/ping")))))))))) - (fact "swagger via api-options" + (testing "swagger via api-options" - (fact "with defaults" + (testing "with defaults" (let [app (api)] - (fact "api-docs are not mounted" + (testing "api-docs are not mounted" (let [[status body] (raw-get* app "/")] - status => nil)) + (is (nil? status)))) - (fact "spec is not mounted" + (testing "spec is not mounted" (let [[status body] (get* app "/swagger.json")] - status => nil)))) + (is (= nil status)))))) - (fact "with spec" + (testing "with spec" (let [app (api {:swagger {:spec "/swagger.json"}})] - (fact "api-docs are not mounted" + (testing "api-docs are not mounted" (let [[status body] (raw-get* app "/")] - status => nil)) + (is (= nil status)))) - (fact "spec is mounted to /swagger.json" + (testing "spec is mounted to /swagger.json" (let [[status body] (get* app "/swagger.json")] - status => 200 - body => (contains {:swagger "2.0"})))))) + (is-200-status status) + (is (= "2.0" (:swagger body)))))))) - (fact "with ui" + (testing "with ui" (let [app (api {:swagger {:ui "/api-docs"}})] - (fact "api-docs are mounted" + (testing "api-docs are mounted" (let [[status body] (raw-get* app "/api-docs")] - status => 200 - body => #"<title>Swagger UI</title>")) + (is-200-status status) + (is (str/includes? body "<title>Swagger UI</title>")))) - (fact "spec is not mounted" + (testing "spec is not mounted" (let [[status body] (get* app "/swagger.json")] - status => nil)))) + (is (= nil status)))))) - (fact "with ui and spec" + (testing "with ui and spec" (let [app (api {:swagger {:spec "/swagger.json", :ui "/api-docs"}})] - (fact "api-docs are mounted" + (testing "api-docs are mounted" (let [[status body] (raw-get* app "/api-docs")] - status => 200 - body => #"<title>Swagger UI</title>")) + (is-200-status status) + (str/includes? body "<title>Swagger UI</title>"))) - (fact "spec is mounted to /swagger.json" + (testing "spec is mounted to /swagger.json" (let [[status body] (get* app "/swagger.json")] - status => 200 - body => (contains {:swagger "2.0"})))))) + (is-200-status status) + (is (= "2.0" (:swagger body)))))))) -(facts "swagger-docs with anonymous Return and Body models" +(deftest swagger-docs-with-anonymous-Return-and-Body-models-test (let [app (api (swagger-routes) (POST "/echo" [] @@ -629,19 +633,19 @@ :body [_ (s/maybe {:a String})] identity))] - (fact "api-docs" + (testing "api-docs" (let [spec (get-spec app)] (let [operation (some-> spec :paths vals first :post) body-ref (some-> operation :parameters first :schema :$ref) return-ref (get-in operation [:responses :200 :schema :$ref])] - (fact "generated body-param is found in Definitions" - (find-definition spec body-ref) => truthy) + (testing "generated body-param is found in Definitions" + (is (find-definition spec body-ref))) - (fact "generated return-param is found in Definitions" - return-ref => truthy - (find-definition spec body-ref) => truthy)))))) + (testing "generated return-param is found in Definitions" + (is return-ref) + (is (find-definition spec body-ref)))))))) (def Boundary {:type (s/enum "MultiPolygon" "Polygon" "MultiPoint" "Point") @@ -650,7 +654,8 @@ (def ReturnValue {:boundary (s/maybe Boundary)}) -(facts "https://github.com/metosin/compojure-api/issues/53" +;; "https://github.com/metosin/compojure-api/issues/53" +(deftest issue-53-test (let [app (api (swagger-routes) (POST "/" [] @@ -658,25 +663,25 @@ :body [_ Boundary] identity))] - (fact "api-docs" + (testing "api-docs" (let [spec (get-spec app)] (let [operation (some-> spec :paths vals first :post) body-ref (some-> operation :parameters first :schema :$ref) return-ref (get-in operation [:responses :200 :schema :$ref])] - (fact "generated body-param is found in Definitions" - (find-definition spec body-ref) => truthy) + (testing "generated body-param is found in Definitions" + (is (find-definition spec body-ref))) - (fact "generated return-param is found in Definitions" - return-ref => truthy - (find-definition spec body-ref) => truthy)))))) + (testing "generated return-param is found in Definitions" + (is return-ref) + (is (find-definition spec body-ref)))))))) (s/defschema Urho {:kaleva {:kekkonen {s/Keyword s/Any}}}) (s/defschema Olipa {:kerran {:avaruus {s/Keyword s/Any}}}) ; https://github.com/metosin/compojure-api/issues/94 -(facts "preserves deeply nested schema names" +(deftest preserves-deeply-nested-schema-names-test (let [app (api (swagger-routes) (POST "/" [] @@ -684,16 +689,15 @@ :body [_ Olipa] identity))] - (fact "api-docs" + (testing "api-docs" (let [spec (get-spec app)] - (fact "nested models are discovered correctly" - (-> spec :definitions keys set) - - => #{:Urho :UrhoKaleva :UrhoKalevaKekkonen - :Olipa :OlipaKerran :OlipaKerranAvaruus}))))) + (testing "nested models are discovered correctly" + (is (= #{:Urho :UrhoKaleva :UrhoKalevaKekkonen + :Olipa :OlipaKerran :OlipaKerranAvaruus} + (-> spec :definitions keys set)))))))) -(fact "swagger-docs works with the :middleware" +(deftest swagger-docs-works-with-the-middleware-test (let [app (api (swagger-routes) (GET "/middleware" [] @@ -701,16 +705,16 @@ :middleware [[constant-middleware (ok 1)]] (ok 2)))] - (fact "api-docs" - (-> app get-spec :paths vals first) - => {:get {:parameters [{:description "" - :in "query" - :name "x" - :required true - :type "string"}] - :responses {:default {:description ""}}}}))) + (testing "api-docs" + (is (= {:get {:parameters [{:description "" + :in "query" + :name "x" + :required true + :type "string"}] + :responses {:default {:description ""}}}} + (-> app get-spec :paths vals first)))))) -(fact "sub-context paths" +(deftest sub-context-paths-test (let [response {:ping "pong"} ok (ok response) ok? (fn [[status body]] @@ -727,65 +731,60 @@ (GET "/" [] ok) (GET "/b2" [] ok))))] - (fact "valid routes" - (get* app "/") => ok? - (get* app "/a") => ok? - (get* app "/b/b1") => ok? - (get* app "/b") => ok? - (get* app "/b/b2") => ok?) + (testing "valid routes" + (is (ok? (get* app "/"))) + (is (ok? (get* app "/a"))) + (is (ok? (get* app "/b/b1"))) + (is (ok? (get* app "/b"))) + (is (ok? (get* app "/b/b2")))) - (fact "undocumented compojure easter eggs" - (get* app "/b/b1/") => ok? - (get* app "/b/") => ok? - (fact "this is fixed in compojure 1.5.1" - (get* app "/b//") =not=> ok?)) + (testing "undocumented compojure easter eggs" + (is (ok? (get* app "/b/b1/"))) + (is (ok? (get* app "/b/"))) + (testing "this is fixed in compojure 1.5.1" + (is (not (ok? (get* app "/b//")))))) - (fact "swagger-docs have trailing slashes removed" - (->> app get-spec :paths keys) - => (just ["/" "/a" "/b/b1" "/b" "/b/b2"] :in-any-order)))) + (testing "swagger-docs have trailing slashes removed" + (is (= (sort ["/" "/a" "/b/b1" "/b" "/b/b2"]) + (-> app get-spec :paths keys sort)))))) -(fact "formats supported by ring-middleware-format" +(deftest formats-supported-by-ring-middleware-format-test (let [app (api (POST "/echo" [] :body-params [foo :- String] (ok {:foo foo})))] - (tabular - (facts - (fact {:midje/description (str ?content-type " to json")} + (doseq [[?content-type ?body] [["application/json" "{\"foo\":\"bar\"}"] + ["application/edn" "{:foo \"bar\"}"] + ["application/transit+json" "[\"^ \",\"~:foo\",\"bar\"]"]]] + (testing (pr-str [?content-type ?body]) + (testing (str ?content-type " to json") (let [[status body] (raw-post* app "/echo" ?body ?content-type {:accept "application/json"})] - status => 200 - body => "{\"foo\":\"bar\"}")) - (fact {:midje/description (str "json to " ?content-type)} + (is-200-status status) + (is (= "{\"foo\":\"bar\"}" body)))) + (testing (str "json to " ?content-type) (let [[status body] (raw-post* app "/echo" "{\"foo\":\"bar\"}" "application/json" {:accept ?content-type})] - status => 200 - body => ?body))) + (is-200-status status) + (is (= ?body body)))))))) - ?content-type ?body - "application/json" "{\"foo\":\"bar\"}" - "application/edn" "{:foo \"bar\"}" - "application/transit+json" "[\"^ \",\"~:foo\",\"bar\"]"))) - -(fact "multiple routes in context" +(deftest multiple-routes-in-context-test (let [app (api (context "/foo" [] (GET "/bar" [] (ok ["bar"])) (GET "/baz" [] (ok ["baz"]))))] - (fact "first route works" + (testing "first route works" (let [[status body] (get* app "/foo/bar")] - status => 200 - body => ["bar"])) - (fact "second route works" + (is-200-status status) + (is (= ["bar"] body)))) + (testing "second route works" (let [[status body] (get* app "/foo/baz")] - status => 200 - body => ["baz"])))) - -(require '[compojure.api.test-domain :refer [Pizza burger-routes]]) + (is-200-status status) + (is (= ["baz"] body)))))) -(fact "external deep schemas" +(deftest external-deep-schemas-test (let [app (api (swagger-routes) burger-routes @@ -794,22 +793,23 @@ :body [body Pizza] (ok body)))] - (fact "direct route with nested named schema works when called" + (testing "direct route with nested named schema works when called" (let [pizza {:toppings [{:name "cheese"}]} [status body] (post* app "/pizza" (json-string pizza))] - status => 200 - body => pizza)) + (is-200-status status) + (is (= pizza body)))) - (fact "defroute*'d route with nested named schema works when called" + (testing "defroute*'d route with nested named schema works when called" (let [burger {:ingredients [{:name "beef"}, {:name "egg"}]} [status body] (post* app "/burger" (json-string burger))] - status => 200 - body => burger)) + (is-200-status status) + (is (= burger body)))) - (fact "generates correct swagger-spec" - (-> app get-spec :definitions keys set) => #{:Topping :Pizza :Burger :Beef}))) + (testing "generates correct swagger-spec" + (is (= #{:Topping :Pizza :Burger :Beef} + (-> app get-spec :definitions keys set)))))) -(fact "multiple routes with same path & method in same file" +(deftest multiple-routes-with-same-path-and-method-in-same-file-test (let [app (api (swagger-routes) (GET "/ping" [] @@ -819,15 +819,15 @@ :summary "passive-ping" (ok {:ping "passive"})))] - (fact "first route matches with Compojure" + (testing "first route matches with Compojure" (let [[status body] (get* app "/ping" {})] - status => 200 - body => {:ping "active"})) + (is-200-status status) + (is (= {:ping "active"} body)))) - (fact "generates correct swagger-spec" - (-> app get-spec :paths vals first :get :summary) => "active-ping"))) + (testing "generates correct swagger-spec" + (is (= "active-ping" (-> app get-spec :paths vals first :get :summary)))))) -(fact "multiple routes with same path & method over context" +(deftest multiple-routes-with-same-path-and-method-over-context-test (let [app (api (swagger-routes) (context "/api" [] @@ -841,15 +841,16 @@ :summary "passive-ping" (ok {:ping "passive"})))))] - (fact "first route matches with Compojure" + (testing "first route matches with Compojure" (let [[status body] (get* app "/api/ipa/ping" {})] - status => 200 - body => {:ping "active"})) + (is-200-status status) + (is (= {:ping "active"} body)))) - (fact "generates correct swagger-spec" - (-> app get-spec :paths vals first :get :summary) => "active-ping"))) + (testing "generates correct swagger-spec" + (is (= "active-ping" (-> app get-spec :paths vals first :get :summary)))))) -(fact "multiple routes with same overall path (with different path sniplets & method over context" +;; multiple routes with same overall path (with different path sniplets & method over context) +(deftest multiple-routes-with-same-overall-path-test (let [app (api (swagger-routes) (context "/api/ipa" [] @@ -862,72 +863,73 @@ :summary "passive-ping" (ok {:ping "passive"})))))] - (fact "first route matches with Compojure" + (testing "first route matches with Compojure" (let [[status body] (get* app "/api/ipa/ping" {})] - status => 200 - body => {:ping "active"})) + (is-200-status status) + (is (= {:ping "active"} body)))) - (fact "generates correct swagger-spec" - (-> app get-spec :paths vals first :get :summary) => "active-ping"))) + (testing "generates correct swagger-spec" + (is (= "active-ping" (-> app get-spec :paths vals first :get :summary)))))) ; https://github.com/metosin/compojure-api/issues/98 ; https://github.com/metosin/compojure-api/issues/134 -(fact "basePath" +(deftest basePath-test (let [app (api (swagger-routes))] - (fact "no context" - (-> app get-spec :basePath) => "/") + (testing "no context" + (is (= "/" (-> app get-spec :basePath)))) - (fact "app-servers with given context" - (against-background (rsc/context anything) => "/v2") - (-> app get-spec :basePath) => "/v2")) + (testing "app-servers with given context" + (with-redefs [rsc/context (fn [& args] "/v2")] + (is (= "/v2" (-> app get-spec :basePath)))))) (let [app (api (swagger-routes {:data {:basePath "/serve/from/here"}}))] - (fact "override it" - (-> app get-spec :basePath) => "/serve/from/here")) + (testing "override it" + (is (= "/serve/from/here" (-> app get-spec :basePath))))) (let [app (api (swagger-routes {:data {:basePath "/"}}))] - (fact "can set it to the default" - (-> app get-spec :basePath) => "/"))) + (testing "can set it to the default" + (is (= "/" (-> app get-spec :basePath)))))) -(fact "multiple different models with same name" +(deftest multiple-different-models-with-same-name-test - (fact "schemas with same regexps are not equal" - {:d #"\D"} =not=> {:d #"\D"}) + (testing "schemas with same regexps are not equal" + (is (not= {:d #"\D"} {:d #"\D"}))) - (fact "api-spec with 2 schemas with non-equal contents" + (testing "api-spec with 2 schemas with non-equal contents" (let [app (api (swagger-routes) (GET "/" [] :responses {200 {:schema (s/schema-with-name {:a {:d #"\D"}} "Kikka")} 201 {:schema (s/schema-with-name {:a {:d #"\D"}} "Kikka")}} identity))] - (fact "api spec doesn't fail (#102)" - (get-spec app) => anything)))) + (testing "api spec doesn't fail (#102)" + (is (get-spec app)))))) (def over-the-hills-and-far-away (POST "/" [] :body-params [a :- s/Str] identity)) -(fact "anonymous body models over defined routes" +(deftest anonymous-body-models-over-defined-routes-test (let [app (api (swagger-routes) over-the-hills-and-far-away)] - (fact "generated model doesn't have namespaced keys" - (-> app get-spec :definitions vals first :properties keys first) => :a))) + (testing "generated model doesn't have namespaced keys" + (is (= :a (-> app get-spec :definitions vals first :properties keys first)))))) (def foo (GET "/foo" [] (let [foo {:foo "bar"}] (ok foo)))) -(fact "defroutes with local symbol usage with same name (#123)" +;;defroutes with local symbol usage with same name (#123) +(deftest defroutes-with-local-symbol-usage-with-same-name-test (let [app (api foo)] (let [[status body] (get* app "/foo")] - status => 200 - body => {:foo "bar"}))) + (is-200-status status) + (is (= {:foo "bar"} body))))) (def response-descriptions-routes (GET "/x" [] @@ -935,13 +937,13 @@ :description "Horror"}} identity)) -(fact "response descriptions" +(deftest response-descriptions-test (let [app (api (swagger-routes) response-descriptions-routes)] - (-> app get-spec :paths vals first :get :responses :500 :description) => "Horror")) + (is (= "Horror" (-> app get-spec :paths vals first :get :responses :500 :description))))) -(fact "exceptions options with custom validation error handler" +(deftest exceptions-options-with-custom-validation-error-handler-test (let [app (api {:exceptions {:handlers {::ex/request-validation custom-validation-error-handler ::ex/request-parsing custom-validation-error-handler @@ -954,27 +956,27 @@ 1 (ok 1) (ok "not a number"))))] - (fact "return case, valid request & valid model" + (testing "return case, valid request & valid model" (let [[status body] (post* app "/get-long" "{\"x\": 1}")] - status => 200 - body => 1)) + (is-200-status status) + (is (= 1 body)))) - (fact "return case, not schema valid request" + (testing "return case, not schema valid request" (let [[status body] (post* app "/get-long" "{\"x\": \"1\"}")] - status => 400 - body => (contains {:custom-error "/get-long"}))) + (is (= 400 status)) + (is (= "/get-long" (:custom-error body))))) - (fact "return case, invalid json request" + (testing "return case, invalid json request" (let [[status body] (post* app "/get-long" "{x: 1}")] - status => 400 - body => (contains {:custom-error "/get-long"}))) + (is (= 400 status)) + (is (= "/get-long" (:custom-error body))))) - (fact "return case, valid request & invalid model" + (testing "return case, valid request & invalid model" (let [[status body] (post* app "/get-long" "{\"x\": 2}")] - status => 501 - body => (contains {:custom-error "/get-long"}))))) + (is (= 501 status)) + (is (= "/get-long" (:custom-error body))))))) -(fact "exceptions options with custom exception and error handler" +(deftest exceptions-options-with-custom-exception-and-error-handler-test (let [app (api {:exceptions {:handlers {::ex/default (custom-exception-handler :custom-exception) SQLException (custom-exception-handler :sql-exception) @@ -991,72 +993,72 @@ (GET "/sub-class" [] (throw (SQLWarning.))))] - (fact "uses default exception handler for unknown exceptions" + (testing "uses default exception handler for unknown exceptions" (let [[status body] (get* app "/some-exception")] - status => 200 - body => {:custom-exception "java.lang.RuntimeException"})) + (is-200-status status) + (is (= {:custom-exception "java.lang.RuntimeException"} body)))) - (fact "uses default exception handler for unknown errors" + (testing "uses default exception handler for unknown errors" (let [[status body] (get* app "/some-error")] - status => 200 - (:custom-exception body) => (contains ":data \"some error\""))) + (is-200-status status) + (is (str/includes? (:custom-exception body) ":data \"some error\"" )))) - (fact "uses specific error handler for ::custom-errors" + (testing "uses specific error handler for ::custom-errors" (let [[_ body] (get* app "/specific-error")] - body => {:custom-error "my error"})) + (is (= {:custom-error "my error"} body)))) - (fact "direct class" + (testing "direct class" (let [[_ body] (get* app "/class")] - body => (contains {:sql-exception "java.sql.SQLException"}))) + (is (= "java.sql.SQLException" (:sql-exception body))))) - (fact "sub-class" + (testing "sub-class" (let [[_ body] (get* app "/sub-class")] - body => (contains {:sql-exception "java.sql.SQLWarning"}))))) + (is (= "java.sql.SQLWarning" (:sql-exception body))))))) -(fact "exception handling can be disabled" +(deftest exception-handling-can-be-disabled-test (let [app (api {:exceptions nil} (GET "/throw" [] (throw (new RuntimeException))))] - (get* app "/throw") => throws)) + (is (thrown? RuntimeException (get* app "/throw"))))) (s/defn schema-error [a :- s/Int] {:bar a}) -(fact "handling schema.core/error" +;; handling schema.core/error +(deftest handling-schema-core-error-test (let [app (api {:exceptions {:handlers {:schema.core/error ex/schema-error-handler}}} (GET "/:a" [] :path-params [a :- s/Str] (ok (s/with-fn-validation (schema-error a)))))] (let [[status body] (get* app "/foo")] - status => 400 - body => (contains {:errors vector?})))) + (is (= 400 status)) + (is (-> body :errors vector?))))) -(fact "ring-swagger options" +(deftest ring-swagger-options-test (let [app (api {:ring-swagger {:default-response-description-fn status/get-description}} (swagger-routes) (GET "/ping" [] :responses {500 nil} identity))] - (-> app get-spec :paths vals first :get :responses :500 :description) - => "There was an internal server error.")) + (is (= "There was an internal server error." (-> app get-spec :paths vals first :get :responses :500 :description))))) -(fact "path-for" - (fact "simple case" +(deftest path-for-test + (testing "simple case" (let [app (api (GET "/api/pong" [] :name :pong (ok {:pong "pong"})) (GET "/api/ping" [] (moved-permanently (path-for :pong))))] - (fact "path-for works" + (testing "path-for works" (let [[status body] (get* app "/api/ping" {})] - status => 200 - body => {:pong "pong"})))) + (is-200-status status) + (is (= {:pong "pong"} body)))))) - (fact "with path parameters" + (testing "with path parameters" (let [app (api (GET "/lost-in/:country/:zip" [] :name :lost @@ -1066,25 +1068,24 @@ (GET "/api/ping" [] (moved-permanently (path-for :lost {:country :FI, :zip 33200}))))] - (fact "path-for resolution" + (testing "path-for resolution" (let [[status body] (get* app "/api/ping" {})] - status => 200 - body => {:country "FI" - :zip 33200})))) + (is-200-status status) + (is (= {:country "FI" :zip 33200} body)))))) - (fact "https://github.com/metosin/compojure-api/issues/150" + (testing "https://github.com/metosin/compojure-api/issues/150" (let [app (api (GET "/companies/:company-id/refresh" [] :path-params [company-id :- s/Int] :name :refresh-company :return String (ok (path-for :refresh-company {:company-id company-id}))))] - (fact "path-for resolution" + (testing "path-for resolution" (let [[status body] (get* app "/companies/4/refresh")] - status => 200 - body => "/companies/4/refresh")))) + (is-200-status status) + (is (= "/companies/4/refresh" body)))))) - (fact "multiple routes with same name fail at compile-time" + (testing "multiple routes with same name fail at compile-time" (let [app' `(api (GET "/api/pong" [] :name :pong @@ -1092,115 +1093,113 @@ (GET "/api/ping" [] :name :pong identity))] - (eval app') => (throws RuntimeException))) + (is (thrown? RuntimeException (eval app'))))) - (fact "bindings with wrong syntax should fail nicely" + (testing "bindings with wrong syntax should fail nicely" (let [app' `(api (GET "/api/:id/pong" [] :path-params [id ::id] :name :pong identity))] - (eval app') => (throws RuntimeException)))) + (is (thrown? RuntimeException (eval app')))))) -(fact "swagger-spec-path" - (fact "defaults to /swagger.json" +(deftest swagger-spec-path-test + (testing "defaults to /swagger.json" (let [app (api (swagger-routes))] - (swagger/swagger-spec-path app) => "/swagger.json")) - (fact "follows defined path" + (is (= "/swagger.json" (swagger/swagger-spec-path app))))) + (testing "follows defined path" (let [app (api (swagger-routes {:spec "/api/api-docs/swagger.json"}))] - (swagger/swagger-spec-path app) => "/api/api-docs/swagger.json"))) + (is (= "/api/api-docs/swagger.json" (swagger/swagger-spec-path app)))))) (defrecord NonSwaggerRecord [data]) -(fact "api validation" +(deftest api-validation-test - (fact "a swagger api with valid swagger records" + (testing "a swagger api with valid swagger records" (let [app (api (swagger-routes) (GET "/ping" [] :return {:data s/Str} (ok {:data "ping"})))] - (fact "works" + (testing "works" (let [[status body] (get* app "/ping")] - status => 200 - body => {:data "ping"})) + (is-200-status status) + (is (= {:data "ping"} body)))) - (fact "the api is valid" - (validator/validate app) => app))) + (testing "the api is valid" + (is (= app (validator/validate app)))))) - (fact "a swagger api with invalid swagger records" + (testing "a swagger api with invalid swagger records" (let [app (api (swagger-routes) (GET "/ping" [] :return NonSwaggerRecord (ok (->NonSwaggerRecord "ping"))))] - (fact "works" + (testing "works" (let [[status body] (get* app "/ping")] - status => 200 - body => {:data "ping"})) - - (fact "the api is invalid" - (validator/validate app) - => (throws - IllegalArgumentException - (str - "don't know how to convert class compojure.api.integration_test.NonSwaggerRecord " - "into a Swagger Schema. Check out ring-swagger docs for details."))))) - - (fact "a non-swagger api with invalid swagger records" + (is-200-status status) + (is (= {:data "ping"} body)))) + + (testing "the api is invalid" + (is (thrown-with-msg? + IllegalArgumentException + #"don't know how to convert class compojure.api.integration_test.NonSwaggerRecord into a Swagger Schema. Check out ring-swagger docs for details." + (validator/validate app)))))) + + (testing "a non-swagger api with invalid swagger records" (let [app (api (GET "/ping" [] :return NonSwaggerRecord (ok (->NonSwaggerRecord "ping"))))] - (fact "works" + (testing "works" (let [[status body] (get* app "/ping")] - status => 200 - body => {:data "ping"})) + (is-200-status status) + (is (= {:data "ping"} body)))) - (fact "the api is valid" - (validator/validate app) => app)))) + (testing "the api is valid" + (is (= app (validator/validate app))))))) -(fact "component integration" +(deftest component-integration-test (let [system {:magic 42}] - (fact "via options" + (testing "via options" (let [app (api {:components system} (GET "/magic" [] :components [magic] (ok {:magic magic})))] (let [[status body] (get* app "/magic")] - status => 200 - body => {:magic 42}))) + (is-200-status status) + (is (= {:magic 42} body))))) - (fact "via middleware" + (testing "via middleware" (let [handler (api (GET "/magic" [] :components [magic] (ok {:magic magic}))) app (mw/wrap-components handler system)] (let [[status body] (get* app "/magic")] - status => 200 - body => {:magic 42}))))) + (is-200-status status) + (is (= {:magic 42} body))))))) -(fact "sequential string parameters" +(deftest sequential-string-parameters-test (let [app (api (GET "/ints" [] :query-params [i :- [s/Int]] (ok {:i i})))] - (fact "multiple values" + (testing "multiple values" (let [[status body] (get* app "/ints?i=1&i=2&i=3")] - status => 200 - body => {:i [1, 2, 3]})) - (fact "single value" + (is-200-status status) + (is (= {:i [1, 2, 3]} body)))) + (testing "single value" (let [[status body] (get* app "/ints?i=42")] - status => 200 - body => {:i [42]})))) + (is-200-status status) + (is (= {:i [42]} body)))))) -(fact ":swagger params just for ducumentation" - (fact "compile-time values" +(deftest swagger-params-just-for-documentation-test + (testing "compile-time values" (let [app (api (swagger-routes) (GET "/route" [q] @@ -1210,23 +1209,23 @@ :parameters {:query {:q s/Bool}}} (ok {:q q})))] - (fact "there is no coercion" + (testing "there is no coercion" (let [[status body] (get* app "/route" {:q "kikka"})] - status => 200 - body => {:q "kikka"})) - - (fact "swagger-docs are generated" - (-> app get-spec :paths vals first :get) - => (contains - {:x-name "boolean" - :operationId "echoBoolean" - :description "Echoes a boolean" - :parameters [{:description "" - :in "query" - :name "q" - :required true - :type "boolean"}]})))) - (fact "run-time values" + (is-200-status status) + (is (= {:q "kikka"} body)))) + + (testing "swagger-docs are generated" + (is (= {:x-name "boolean" + :operationId "echoBoolean" + :description "Echoes a boolean" + :parameters [{:description "" + :in "query" + :name "q" + :required true + :type "boolean"}]} + (-> app get-spec :paths vals first :get + (select-keys [:x-name :operationId :description :parameters]))))))) + (testing "run-time values" (let [runtime-data {:x-name :boolean :operationId "echoBoolean" :description "Echoes a boolean" @@ -1237,24 +1236,25 @@ :swagger runtime-data (ok {:q q})))] - (fact "there is no coercion" + (testing "there is no coercion" (let [[status body] (get* app "/route" {:q "kikka"})] - status => 200 - body => {:q "kikka"})) - - (fact "swagger-docs are generated" - (-> app get-spec :paths vals first :get) - => (contains - {:x-name "boolean" - :operationId "echoBoolean" - :description "Echoes a boolean" - :parameters [{:description "" - :in "query" - :name "q" - :required true - :type "boolean"}]}))))) - -(fact "swagger-docs via api options, #218" + (is-200-status status) + (is (= {:q "kikka"} body)))) + + (testing "swagger-docs are generated" + (is (= {:x-name "boolean" + :operationId "echoBoolean" + :description "Echoes a boolean" + :parameters [{:description "" + :in "query" + :name "q" + :required true + :type "boolean"}]} + (-> app get-spec :paths vals first :get + (select-keys [:x-name :operationId :description :parameters])))))))) + +;; swagger-docs via api options, #218 +(deftest swagger-docs-via-api-options (let [routes (routes (context "/api" [] (GET "/ping" [] @@ -1268,14 +1268,15 @@ api1 (api {:swagger {:spec "/swagger.json", :ui "/"}} routes) api2 (api (swagger-routes) routes)] - (fact "both generate same swagger-spec" - (get-spec api1) => (get-spec api2)) + (testing "both generate same swagger-spec" + (is (= (get-spec api1) (get-spec api2)))) - (fact "not-found handler works" - (second (get* api1 "/missed")) => {:message "404"} - (second (get* api2 "/missed")) => {:message "404"}))) + (testing "not-found handler works" + (is (= {:message "404"} (second (get* api1 "/missed")))) + (is (= {:message "404"} (second (get* api2 "/missed"))))))) -(fact "more swagger-data can be (deep-)merged in - either via swagger-docs at runtime via mws, fixes #170" +;; more swagger-data can be (deep-)merged in - either via swagger-docs at runtime via mws, fixes #170 +(deftest issue-170-test (let [app (api (route-middleware [[rsm/wrap-swagger-data {:paths {"/runtime" {:get {}}}}]] (swagger-routes @@ -1283,30 +1284,29 @@ {:info {:version "2.0.0"} :paths {"/extra" {:get {}}}}}) (GET "/normal" [] (ok))))] - (get-spec app) => (contains - {:paths (just - {"/normal" irrelevant - "/extra" irrelevant - "/runtime" irrelevant})}))) + (is (= #{"/normal" "/extra" "/runtime"} + (-> (get-spec app) :paths keys set))))) -(fact "handling invalid routes with api" +(deftest handling-invalid-routes-with-api-test (let [invalid-routes (routes (constantly nil))] - (fact "by default, logs the exception" - (api invalid-routes) => truthy - (provided - (compojure.api.impl.logging/log! :warn irrelevant) => irrelevant :times 1)) + (testing "by default, logs the exception" + (let [a (atom [])] + (with-redefs [compojure.api.impl.logging/log! (fn [& args] (swap! a conj args))] + (is (api invalid-routes))) + (is (= [:warn] (map first @a))))) - (fact "ignoring invalid routes doesn't log" - (api {:api {:invalid-routes-fn nil}} invalid-routes) => truthy - (provided - (compojure.api.impl.logging/log! :warn irrelevant) => irrelevant :times 0)) + (testing "ignoring invalid routes doesn't log" + (let [a (atom [])] + (with-redefs [compojure.api.impl.logging/log! (fn [& args] (swap! a conj args))] + (is (api {:api {:invalid-routes-fn nil}} invalid-routes))) + (is (empty? @a)))) - (fact "throwing exceptions" - (api {:api {:invalid-routes-fn routes/fail-on-invalid-child-routes}} invalid-routes)) => throws)) + (testing "throwing exceptions" + (is (thrown? Exception (api {:api {:invalid-routes-fn routes/fail-on-invalid-child-routes}} invalid-routes)))))) -(fact "using local symbols for restructuring params" +(deftest using-local-symbols-for-restructuring-params-test (let [responses {400 {:schema {:fail s/Str}}} app (api {:swagger {:spec "/swagger.json" @@ -1319,32 +1319,39 @@ :responses (assoc responses 500 {:schema {:m s/Str}}) :return {:ok s/Str} (ok))) - paths (:paths (get-spec app))] - - (get-in paths ["/a" :get :responses]) - => (just {:400 (just {:schema anything :description ""}) - :200 (just {:schema anything :description ""})}) - - (get-in paths ["/b" :get :responses]) - => (just {:400 (just {:schema anything :description ""}) - :200 (just {:schema anything :description ""}) - :500 (just {:schema anything :description ""})}))) - -(fact "when functions are returned" + paths (:paths (get-spec app)) + a-resp (get-in paths ["/a" :get :responses]) + b-resp (get-in paths ["/b" :get :responses])] + + (is (= #{:200 :400} (-> a-resp keys set))) + (is (= #{:schema :description} (-> a-resp :400 keys set))) + (is (= #{:schema :description} (-> a-resp :200 keys set))) + (is (= "" (-> a-resp :400 :description))) + (is (= "" (-> a-resp :200 :description))) + + (is (= #{:200 :400 :500} (-> b-resp keys set))) + (is (= #{:schema :description} (-> b-resp :500 keys set))) + (is (= #{:schema :description} (-> b-resp :400 keys set))) + (is (= #{:schema :description} (-> b-resp :200 keys set))) + (is (= "" (-> b-resp :500 :description))) + (is (= "" (-> b-resp :400 :description))) + (is (= "" (-> b-resp :200 :description))))) + +(deftest when-functions-are-returned-test (let [wrap-mw-params (fn [handler value] (fn [request] (handler (update request ::mw #(str % value)))))] - (fact "from endpoint" + (testing "from endpoint" (let [app (GET "/ping" [] :middleware [[wrap-mw-params "1"]] :query-params [{a :- s/Str "a"}] (fn [req] (str (::mw req) a)))] - (app {:request-method :get, :uri "/ping", :query-params {}}) => (contains {:body "1a"}) - (app {:request-method :get, :uri "/ping", :query-params {:a "A"}}) => (contains {:body "1A"}))) + (is (= "1a" (:body (app {:request-method :get, :uri "/ping", :query-params {}})))) + (is (= "1A" (:body (app {:request-method :get, :uri "/ping", :query-params {:a "A"}})))))) - (fact "from endpoint under context" + (testing "from endpoint under context" (let [app (context "/api" [] :middleware [[wrap-mw-params "1"]] :query-params [{a :- s/Str "a"}] @@ -1353,9 +1360,9 @@ :query-params [{b :- s/Str "b"}] (fn [req] (str (::mw req) a b))))] - (app {:request-method :get, :uri "/api/ping", :query-params {}}) => (contains {:body "12ab"}) - (app {:request-method :get, :uri "/api/ping", :query-params {:a "A"}}) => (contains {:body "12Ab"}) - (app {:request-method :get, :uri "/api/ping", :query-params {:a "A", :b "B"}}) => (contains {:body "12AB"}))))) + (is (= "12ab" (:body (app {:request-method :get, :uri "/api/ping", :query-params {}})))) + (is (= "12Ab" (:body (app {:request-method :get, :uri "/api/ping", :query-params {:a "A"}})))) + (is (= "12AB" (:body (app {:request-method :get, :uri "/api/ping", :query-params {:a "A", :b "B"}})))))))) (defn check-for-response-handler "This response-validation handler checks for the existence of :response in its input. If it's there, it @@ -1365,7 +1372,7 @@ (ok {:message "Found :response in data!" :attempted-body (get-in data [:response :body])}) (not-found "No :response key present in data!"))) -(fact "response-validation handler has access to response value that failed coercion" +(deftest response-validation-handler-has-access-to-response-value-that-failed-coercion-test (let [incorrect-return-value {:incorrect "response"} app (api {:exceptions {:handlers {::ex/response-validation check-for-response-handler}}} @@ -1375,46 +1382,50 @@ ; This should fail and trigger our error handler (ok incorrect-return-value)))] - (fact "return case, valid request & valid model" + (testing "return case, valid request & valid model" (let [[status body] (get* app "/test-response")] - status => 200 - (:attempted-body body) => incorrect-return-value)))) + (is-200-status status) + (is (= incorrect-return-value (:attempted-body body))))))) -(fact "correct swagger parameter order with small number or parameters, #224" +;; "correct swagger parameter order with small number or parameters, #224" +(deftest issue-224-test (let [app (api (swagger-routes) (GET "/ping" [] :query-params [a b c d e] (ok {:a a, :b b, :c c, :d d, :e e})))] - (fact "api works" + (testing "api works" (let [[status body] (get* app "/ping" {:a "A" :b "B" :c "C" :d "D" :e "E"})] - status => 200 - body => {:a "A" :b "B" :c "C" :d "D" :e "E"})) - (fact "swagger parameters are in correct order" - (-> app get-spec :paths (get "/ping") :get :parameters (->> (map (comp keyword :name)))) => [:a :b :c :d :e]))) - -(fact "empty top-level route, #https://github.com/metosin/ring-swagger/issues/92" + (is-200-status status) + (is (= {:a "A" :b "B" :c "C" :d "D" :e "E"} body)))) + (testing "swagger parameters are in correct order" + (is (= [:a :b :c :d :e] + (-> app get-spec :paths (get "/ping") :get :parameters (->> (map (comp keyword :name))))))))) + +;; empty top-level route, #https://github.com/metosin/ring-swagger/issues/92 +(deftest issue-92-test (let [app (api {:swagger {:spec "/swagger.json"}} (GET "/" [] (ok {:kikka "kukka"})))] - (fact "api works" + (testing "api works" (let [[status body] (get* app "/")] - status => 200 - body => {:kikka "kukka"})) - (fact "swagger docs" - (-> app get-spec :paths keys) => ["/"]))) + (is-200-status status) + (is (= {:kikka "kukka"} body)))) + (testing "swagger docs" + (is (= ["/"] (-> app get-spec :paths keys)))))) -(fact "describe works on anonymous bodys, #168" +;; describe works on anonymous bodys, #168 +(deftest issue-168-test (let [app (api (swagger-routes) (POST "/" [] :body [body (describe {:kikka [{:kukka String}]} "kikkas")] (ok body)))] - (fact "description is in place" - (-> app get-spec :paths (get "/") :post :parameters first) - => (contains {:description "kikkas"})))) + (testing "description is in place" + (is (= "kikkas" (-> app get-spec :paths (get "/") :post :parameters first :description)))))) -(facts "swagger responses headers are mapped correctly, #232" +;; swagger responses headers are mapped correctly, #232 +(deftest issue-232-test (let [app (api (swagger-routes) (context "/resource" [] @@ -1422,25 +1433,26 @@ {:get {:responses {200 {:schema {:size s/Str} :description "size" :headers {"X-men" (describe s/Str "mutant")}}}}})))] - (-> app get-spec :paths vals first :get :responses :200 :headers) - => {:X-men {:description "mutant", :type "string"}})) + (is (= {:X-men {:description "mutant", :type "string"}} + (-> app get-spec :paths vals first :get :responses :200 :headers))))) -(facts "api-middleware can be disabled" +(deftest api-middleware-can-be-disabled-test (let [app (api {:api {:disable-api-middleware? true}} (swagger-routes) (GET "/params" [x] (ok {:x x})) (GET "/throw" [] (throw (RuntimeException. "kosh"))))] - (fact "json-parsing & wrap-params is off" + (testing "json-parsing & wrap-params is off" (let [[status body] (raw-get* app "/params" {:x 1})] - status => 200 - body => {:x nil})) + (is-200-status status) + (is (= {:x nil} body)))) - (fact "exceptions are not caught" - (raw-get* app "/throw") => throws))) + (testing "exceptions are not caught" + (is (thrown? Exception (raw-get* app "/throw")))))) -(facts "custom formats contribute to Swagger :consumes & :produces" +;"custom formats contribute to Swagger :consumes & :produces" +(deftest custom-formats-contribute-to-Swagger-consumes-produces-test (let [custom-type "application/vnd.vendor.v1+json" app (api {:swagger {:spec "/swagger.json"} @@ -1451,32 +1463,34 @@ :body [data {:kikka s/Str}] (ok data)))] - (fact "it works" + (testing "it works" (let [response (app {:uri "/echo" :request-method :post :body (json-stream {:kikka "kukka"}) :headers {"content-type" "application/vnd.vendor.v1+json" "accept" "application/vnd.vendor.v1+json"}})] - (-> response :body slurp) => (json-string {:kikka "kukka"}) - (-> response :headers) => (contains {"Content-Type" "application/vnd.vendor.v1+json; charset=utf-8"}))) + (is (= (json-string {:kikka "kukka"}) (-> response :body slurp))) + (is (= "application/vnd.vendor.v1+json; charset=utf-8" + (-> response :headers (get "Content-Type")))))) - (fact "spec is correct" - (get-spec app) - => (contains - {:produces (just ["application/vnd.vendor.v1+json" "application/json"] :in-any-order) - :consumes (just ["application/vnd.vendor.v1+json" "application/json"] :in-any-order)})))) + (testing "spec is correct" + (let [res (get-spec app)] + (is (= (sort ["application/vnd.vendor.v1+json" "application/json"]) + (-> res :produces sort))) + (is (= (sort ["application/vnd.vendor.v1+json" "application/json"]) + (-> res :consumes sort))))))) -(facts "muuntaja is bound in request" +(deftest muuntaja-is-bound-in-request-test (let [app (api (GET "/ping" {:keys [::request/muuntaja]} (ok {:pong (slurp (m/encode muuntaja "application/json" {:is "json"}))})))] (let [[status body] (get* app "/ping")] - status => 200 - body => {:pong "{\"is\":\"json\"}"}))) + (is-200-status status) + (is (= {:pong "{\"is\":\"json\"}"} body))))) -(facts ":body doesn't keywordize keys" +(deftest body-doesnt-keywordize-keys-test (let [m (m/create) data {:items {"kikka" 42}} body* (atom nil) @@ -1491,23 +1505,23 @@ (reset! body* body) (ok)))] - (facts ":body-params keywordizes params" - (app {:uri "/echo" - :request-method :post - :body (m/encode m "application/transit+json" data) - :headers {"content-type" "application/transit+json" - "accept" "application/transit+json"}}) => http/ok? - @body* => {:items {:kikka 42}}) - - (facts ":body does not keywordizes params" - (app {:uri "/echo2" - :request-method :post - :body (m/encode m "application/transit+json" data) - :headers {"content-type" "application/transit+json" - "accept" "application/transit+json"}}) => http/ok? - @body* => {:items {"kikka" 42}}) - - (facts "swagger spec is generated both ways" + (testing ":body-params keywordizes params" + (is (http/ok? (app {:uri "/echo" + :request-method :post + :body (m/encode m "application/transit+json" data) + :headers {"content-type" "application/transit+json" + "accept" "application/transit+json"}}))) + (is (= {:items {:kikka 42}} @body*))) + + (testing ":body does not keywordizes params" + (is (http/ok? (app {:uri "/echo2" + :request-method :post + :body (m/encode m "application/transit+json" data) + :headers {"content-type" "application/transit+json" + "accept" "application/transit+json"}}))) + (is (= {:items {"kikka" 42}} @body*))) + + (testing "swagger spec is generated both ways" (let [spec (get-spec app) echo-schema-name (-> (get-in spec [:paths "/echo" :post :parameters 0 :name]) name (str "Items") keyword) @@ -1515,16 +1529,16 @@ name (str "Items") keyword) echo-schema (get-in spec [:definitions echo-schema-name :properties]) echo2-schema (get-in spec [:definitions echo2-schema-name :properties])] - echo-schema => {:kikka {:type "integer", :format "int64"}} - echo2-schema => {:kikka {:type "integer", :format "int64"}})))) + (is (= {:kikka {:type "integer", :format "int64"}} echo-schema)) + (is (= {:kikka {:type "integer", :format "int64"}} echo2-schema)))))) (def ^:dynamic *response* nil) -(facts "format-based body & response coercion" +(deftest format-based-body-and-response-coercion-test (let [m (mw/create-muuntaja)] - (facts "application/transit & application/edn validate request & response (no coercion)" + (testing "application/transit & application/edn validate request & response (no coercion)" (let [valid-data {:items {"kikka" :kukka}} invalid-data {"items" {"kikka" :kukka}} Schema {:items {(s/required-key "kikka") s/Keyword}} @@ -1535,22 +1549,22 @@ (ok *response*)))] (doseq [format ["application/transit+json" "application/edn"]] - (fact {:midje/description format} + (testing format - (fact "fails with invalid body" - (app (ring-request m format invalid-data)) => http/bad-request?) + (testing "fails with invalid body" + (is (http/bad-request? (app (ring-request m format invalid-data))))) - (fact "fails with invalid response" + (testing "fails with invalid response" (binding [*response* invalid-data] - (app (ring-request m format valid-data)) => http/internal-server-error?)) + (is (http/internal-server-error? (app (ring-request m format valid-data)))))) - (fact "succeeds with valid body & response" + (testing "succeeds with valid body & response" (binding [*response* valid-data] (let [response (app (ring-request m format valid-data))] - response => http/ok? - (m/decode m format (:body response)) => valid-data))))))) + (is (http/ok? response)) + (is (= valid-data (m/decode m format (:body response))))))))))) - (facts "application/json - coerce request, validate response" + (testing "application/json - coerce request, validate response" (let [valid-data {:int 1, :keyword "kikka"} valid-response-data {:int 1, :keyword :kikka} invalid-data {:int "1", :keyword "kikka"} @@ -1562,43 +1576,43 @@ (ok *response*)))] (doseq [format ["application/json"]] - (fact {:midje/description format} + (testing format - (fact "fails with invalid body" - (app (ring-request m format invalid-data)) => http/bad-request?) + (testing "fails with invalid body" + (is (http/bad-request? (app (ring-request m format invalid-data))))) - (fact "fails with invalid response" + (testing "fails with invalid response" (binding [*response* invalid-data] - (app (ring-request m format valid-data)) => http/internal-server-error?)) + (is (http/internal-server-error? (app (ring-request m format valid-data)))))) - (fact "does not coerce response" + (testing "does not coerce response" (binding [*response* valid-data] - (app (ring-request m format valid-data)) => http/internal-server-error?)) + (is (http/internal-server-error? (app (ring-request m format valid-data)))))) - (fact "succeeds with valid body & response" + (testing "succeeds with valid body & response" (binding [*response* valid-response-data] (let [response (app (ring-request m format valid-data))] - response => http/ok? - (m/decode m format (:body response)) => valid-data))))))))) + (is (http/ok? response)) + (is (= valid-data (m/decode m format (:body response))))))))))))) -(fact "static contexts just work" +(deftest static-contexts-just-work-test (let [app (context "/:a" [a] (GET "/:b" [b] (ok [a b])))] - (app {:request-method :get, :uri "/a/b"}) => (contains {:body ["a" "b"]}) - (app {:request-method :get, :uri "/b/c"}) => (contains {:body ["b" "c"]}))) + (is (= ["a" "b"] (:body (app {:request-method :get, :uri "/a/b"})))) + (is (= ["b" "c"] (:body (app {:request-method :get, :uri "/b/c"})))))) -(facts "file responses don't get coerced" +(deftest file-responses-dont-get-coerced-test (let [app (api (swagger-routes) (GET "/file" [] :return File (ok (io/file "project.clj"))))] (let [{:keys [status body]} (app {:uri "/file", :request-method :get})] - status => 200 - body => (partial instance? File)))) + (is-200-status status) + (is (instance? File body))))) -(fact "nil routes are ignored" +(deftest nil-routes-are-ignored-test (let [create-app (fn [{:keys [dev?]}] (context "/api" [] (GET "/ping" [] (ok)) @@ -1609,20 +1623,20 @@ (context "/dev" [] (GET "/tools" [] (ok))))))] - (facts "with routes" + (testing "with routes" (let [app (create-app {:dev? true})] - (app {:request-method :get, :uri "/api/ping"}) => http/ok? - (app {:request-method :get, :uri "/api/db/drop"}) => http/ok? - (app {:request-method :get, :uri "/api/dev/tools"}) => http/ok?)) + (is (http/ok? (app {:request-method :get, :uri "/api/ping"}))) + (is (http/ok? (app {:request-method :get, :uri "/api/db/drop"}))) + (is (http/ok? (app {:request-method :get, :uri "/api/dev/tools"}))))) - (facts "without routes" + (testing "without routes" (let [app (create-app {:dev? false})] - (app {:request-method :get, :uri "/api/ping"}) => http/ok? - (app {:request-method :get, :uri "/api/db/drop"}) => nil - (app {:request-method :get, :uri "/api/dev/tools"}) => nil)))) + (is (http/ok? (app {:request-method :get, :uri "/api/ping"}))) + (is (nil? (app {:request-method :get, :uri "/api/db/drop"}))) + (is (nil? (app {:request-method :get, :uri "/api/dev/tools"}))))))) -(facts "wrap-routes" - (fact "simple middleware" +(deftest wrap-routes-test + (testing "simple middleware" (let [called? (atom false) app (api (route-middleware @@ -1636,17 +1650,17 @@ (ok {:ok true}))) response (app {:uri "/a" :request-method :get})] - (-> response :body slurp) => (json-string {:ok true}) - (fact "middleware is called" - @called? => truthy) + (is (= (json-string {:ok true}) (-> response :body slurp))) + (testing "middleware is called" + (is @called?)) (reset! called? false) (let [response (app {:uri "/b" :request-method :get})] - (-> response :body slurp) => (json-string {:ok true}) - @called? => falsey))) + (is (= (json-string {:ok true}) (-> response :body slurp))) + (is (not @called?))))) - (fact "middleware with args" + (testing "middleware with args" (let [mw-value (atom nil) app (api (route-middleware @@ -1661,33 +1675,34 @@ (ok {:ok true}))) response (app {:uri "/a" :request-method :get})] - (-> response :body slurp) => (json-string {:ok true}) - (fact "middleware is called" - @mw-value => :foo-bar) + (is (= (json-string {:ok true}) (-> response :body slurp))) + (testing "middleware is called" + (is (= :foo-bar @mw-value))) (reset! mw-value nil) (let [response (app {:uri "/b" :request-method :get})] - (-> response :body slurp) => (json-string {:ok true}) - @mw-value => nil)))) + (is (= (json-string {:ok true}) (-> response :body slurp))) + (is (nil? @mw-value)))))) -(facts "ring-handler" +(deftest ring-handler-test (let [app (api (GET "/ping" [] (ok))) ring-app (c/ring-handler app)] - (fact "both work" - (get* app "/ping") => (contains 200) - (get* ring-app "/ping") => (contains 200)) - (fact "ring-app is also a Fn" - app =not=> fn? - ring-app => fn?))) - -(fact ":body-params are set to :params" + (testing "both work" + (is (some #{200} (get* app "/ping"))) + (is (some #{200} (get* ring-app "/ping")))) + (testing "ring-app is also a Fn" + (is (not (fn? app))) + (is (fn? ring-app))))) + +(deftest body-params-are-set-to-params-test (let [app (api (POST "/echo" [x] (ok {:x x}))) [status body] (post* app "/echo" (json-string {:x 1}))] - status => 200 - body => {:x 1})) + (is-200-status status) + (is (= {:x 1} body)))) -(facts "body in error handling, #306 & #313" +;; #306 & #313" +(deftest body-in-error-handling-test (let [app (api {:exceptions {:handlers @@ -1697,45 +1712,45 @@ (POST "/error" [] (throw (RuntimeException. "error")))) [status body] (post* app "/error" (json-string {:kikka 6}))] - status => 500 - body => {:kikka 6})) + (is (= 500 status)) + (is (= {:kikka 6} body)))) -(fact "sequential routes" +(deftest sequential-routes-test - (fact "context" + (testing "context" (let [app (api (context "/api" [] (for [path ["/ping" "/pong"]] (GET path [] (ok {:path path})))))] - (fact "all routes can be invoked" + (testing "all routes can be invoked" (let [[status body] (get* app "/api/ping")] - status => 200 - body => {:path "/ping"}) + (is-200-status status) + (is (= {:path "/ping"} body))) (let [[status body] (get* app "/api/pong")] - status => 200 - body => {:path "/pong"})))) + (is-200-status status) + (is (= {:path "/pong"} body)))))) - (fact "routes" + (testing "routes" (let [app (api (routes (for [path ["/ping" "/pong"]] (GET path [] (ok {:path path})))))] - (fact "all routes can be invoked" + (testing "all routes can be invoked" (let [[status body] (get* app "/ping")] - status => 200 - body => {:path "/ping"}) + (is-200-status status) + (is (= {:path "/ping"} body))) (let [[status body] (get* app "/pong")] - status => 200 - body => {:path "/pong"}))))) + (is-200-status status) + (is (= {:path "/pong"} body))))))) -(fact "wrap-format, #374" +(deftest wrap-format-issue-374-test (let [data {:war "hammer"}] - (fact "first api consumes the body" + (testing "first api consumes the body" (let [app (routes (api (POST "/echo1" [] @@ -1746,16 +1761,16 @@ :body [body s/Any] (ok body))))] - (fact "first api sees the body" + (testing "first api sees the body" (let [[status body] (post* app "/echo1" (json-string data))] - status => 200 - body => data)) + (is-200-status status) + (is (= data body)))) - (fact "second api fails" + (testing "second api fails" (let [[status] (post* app "/echo2" (json-string data))] - status => 400))) + (is (= 400 status))))) - (fact "wrap-format with defaults" + (testing "wrap-format with defaults" (let [app (-> (routes (api (POST "/echo1" [] @@ -1768,17 +1783,17 @@ (ok body)))) (mw/wrap-format))] - (fact "first api sees the body" + (testing "first api sees the body" (let [[status body] (post* app "/echo1" (json-string data))] - status => 200 - body => data)) + (is-200-status status) + (is (= data body)))) - (fact "second api sees it too!" + (testing "second api sees it too!" (let [[status body] (post* app "/echo2" (json-string data))] - status => 200 - body => data)))) + (is-200-status status) + (is (= data body)))))) - (fact "wrap-format with configuration" + (testing "wrap-format with configuration" (let [muuntaja (m/create (m/select-formats m/default-options @@ -1799,34 +1814,35 @@ (mw/wrap-format {:formats muuntaja}))] - (fact "first api sees the body" + (testing "first api sees the body" (let [[status body] (post* app "/echo1" (json-string data))] - status => 200 - body => data)) + (is-200-status status) + (is (= data body)))) - (fact "second api sees it too!" + (testing "second api sees it too!" (let [[status body] (post* app "/echo2" (json-string data))] - status => 200 - body => data)) + (is-200-status status) + (is (= data body)))) - (fact "top-level muuntaja effect both" + (testing "top-level muuntaja effect both" (let [[status body] (get* app "/swagger1.json")] - status => 200 - body => (contains - {:produces ["application/json"] - :consumes ["application/json"]})) + (is-200-status status) + (is (= {:produces ["application/json"] + :consumes ["application/json"]} + (select-keys body [:produces :consumes])))) (let [[status body] (get* app "/swagger2.json")] - status => 200 - body => (contains - {:produces ["application/json"] - :consumes ["application/json"]})))))))) + (is-200-status status) + (is (= {:produces ["application/json"] + :consumes ["application/json"]} + (select-keys body [:produces :consumes])))))))))) -(fact "2.* will fail fast with :format" +;;"2.* will fail fast with :format" +(deftest compojure-2x-will-fail-fast-with-format-test (let [app' `(api {:format (m/create)})] - (eval app') => (throws AssertionError))) + (is (thrown? AssertionError (eval app'))))) -(fact "Muuntaja 0.6.0 options" - (fact "new formats" +(deftest Muuntaja-0-6-0-options-test + (testing "new formats" (let [muuntaja (m/create (-> m/default-options (m/install muuntaja.format.msgpack/format) @@ -1843,12 +1859,12 @@ "application/x-yaml" "application/transit+json" "application/transit+msgpack"]] - (fact {:midje/description (str "format " (pr-str format))} + (testing (str "format " (pr-str format)) (let [{:keys [status body]} (app (ring-request muuntaja format data))] - status => 200 - (m/decode muuntaja format body) => data))))) + (is-200-status status) + (is (= data (m/decode muuntaja format body)))))))) - (fact "return types" + (testing "return types" (doseq [[return type] {:input-stream ByteArrayInputStream :bytes (class (make-array Byte/TYPE 0)) :output-stream StreamableResponse}] @@ -1856,7 +1872,7 @@ {:formats (assoc m/default-options :return return)} (GET "/" [] (ok {:kikka "kukka"})))] - (fact {:midje/description (str "return " (pr-str return))} + (testing (str "return " (pr-str return)) (let [{:keys [status body]} (app {:uri "/", :request-method :get})] - status => 200 - body => (partial instance? type))))))) + (is-200-status status) + (is (instance? type body)))))))) diff --git a/test/compojure/api/middleware_test.clj b/test/compojure/api/middleware_test.clj index c06e0bc9..8b0d6b49 100644 --- a/test/compojure/api/middleware_test.clj +++ b/test/compojure/api/middleware_test.clj @@ -1,7 +1,7 @@ (ns compojure.api.middleware-test (:require [compojure.api.middleware :refer :all] [compojure.api.exception :as ex] - [midje.sweet :refer :all] + [clojure.test :refer [deftest is testing]] [ring.util.http-response :refer [ok]] [ring.util.http-status :as status] [ring.util.test]) @@ -20,24 +20,23 @@ (finally (System/setErr err#))))) -(facts encode? - (tabular - (fact - (encode? nil - {:body ?body - :compojure.api.meta/serializable? ?serializable?}) => ?res) - ?body ?serializable? ?res - 5 true true - 5 false false - "foobar" true true - "foobar" false false +(deftest encode?-test + (doseq [[?body ?serializable? ?res :as test-case] + [[5 true true] + [5 false false] + ["foobar" true true] + ["foobar" false false] + [{:foobar "1"} false true] + [{:foobar "1"} true true] + [[1 2 3] false true] + [[1 2 3] true true] + [(ring.util.test/string-input-stream "foobar") false false]]] - {:foobar "1"} false true - {:foobar "1"} true true - [1 2 3] false true - [1 2 3] true true - - (ring.util.test/string-input-stream "foobar") false false)) + (testing (pr-str test-case) + (is (= (encode? nil + {:body ?body + :compojure.api.meta/serializable? ?serializable?}) + ?res))))) (def default-options (:exceptions api-middleware-defaults)) @@ -50,7 +49,7 @@ (throw value)) (throw (Exception. "Timeout while waiting for the request handler."))))) -(facts "wrap-exceptions" +(deftest wrap-exceptions-test (with-out-str (without-err (let [exception (RuntimeException. "kosh") @@ -60,42 +59,47 @@ async-handler (-> (fn [_ _ raise] (raise exception)) (wrap-exceptions default-options))] - (fact "converts exceptions into safe internal server errors" - (handler {}) => (contains {:status status/internal-server-error - :body (contains {:class exception-class - :type "unknown-exception"})}) - (call-async async-handler {}) => (contains {:status status/internal-server-error - :body (contains {:class exception-class - :type "unknown-exception"})}))))) + (testing "converts exceptions into safe internal server errors" + (is (= {:status status/internal-server-error + :body {:class exception-class + :type "unknown-exception"}} + (-> (handler {}) + (select-keys [:status :body])))) + (is (= {:status status/internal-server-error + :body {:class exception-class + :type "unknown-exception"}} + (-> (call-async async-handler {}) + (select-keys [:status :body])))))))) (with-out-str (without-err - (fact "Thrown ex-info type can be matched" + (testing "Thrown ex-info type can be matched" (let [handler (-> (fn [_] (throw (ex-info "kosh" {:type ::test}))) (wrap-exceptions (assoc-in default-options [:handlers ::test] (fn [ex _ _] {:status 500 :body "hello"}))))] - (handler {}) => (contains {:status status/internal-server-error - :body "hello"}))))) + (is (= {:status status/internal-server-error + :body "hello"} + (select-keys (handler {}) [:status :body]))))))) (without-err - (fact "Default handler logs exceptions to console" + (testing "Default handler logs exceptions to console" (let [handler (-> (fn [_] (throw (RuntimeException. "kosh"))) (wrap-exceptions default-options))] - (with-out-str (handler {})) => "ERROR kosh\n"))) + (is (= "ERROR kosh\n" (with-out-str (handler {}))))))) (without-err - (fact "Default request-parsing handler does not log messages" + (testing "Default request-parsing handler does not log messages" (let [handler (-> (fn [_] (throw (ex-info "Error parsing request" {:type ::ex/request-parsing} (RuntimeException. "Kosh")))) (wrap-exceptions default-options))] - (with-out-str (handler {})) => ""))) + (is (= "" (with-out-str (handler {}))))))) (without-err - (fact "Logging can be added to a exception handler" + (testing "Logging can be added to a exception handler" (let [handler (-> (fn [_] (throw (ex-info "Error parsing request" {:type ::ex/request-parsing} (RuntimeException. "Kosh")))) (wrap-exceptions (assoc-in default-options [:handlers ::ex/request-parsing] (ex/with-logging ex/request-parsing-handler :info))))] - (with-out-str (handler {})) => "INFO Error parsing request\n")))) + (is (= "INFO Error parsing request\n" (with-out-str (handler {})))))))) -(facts "compose-middeleware strips nils aways. #228" +(deftest issue-228-test ; "compose-middeleware strips nils aways. #228" (let [times2-mw (fn [handler] (fn [request] (* 2 (handler request))))] - (((compose-middleware [nil times2-mw nil]) (constantly 3)) anything) => 6)) + (is (= 6 (((compose-middleware [nil times2-mw nil]) (constantly 3)) nil))))) diff --git a/test/compojure/api/resource_test.clj b/test/compojure/api/resource_test.clj index d57e7e30..450f3aed 100644 --- a/test/compojure/api/resource_test.clj +++ b/test/compojure/api/resource_test.clj @@ -2,60 +2,59 @@ (:require [compojure.api.sweet :refer :all] [compojure.api.test-utils :refer :all] [plumbing.core :refer [fnk]] - [midje.sweet :refer :all] + [clojure.test :refer [deftest is testing]] [ring.util.http-response :refer :all] [clojure.core.async :as a] [schema.core :as s] [compojure.api.test-utils :refer [call]]) (:import (clojure.lang ExceptionInfo))) -(defn has-body [expected] - (fn [{:keys [body]}] - (= body expected))) +(defn is-has-body [expected {:keys [body]}] + (is (= body expected))) -(def request-validation-failed? - (throws ExceptionInfo #"Request validation failed")) +(defmacro is-request-validation-failed? [form] + `(is (~'thrown? ExceptionInfo #"Request validation failed" ~form))) -(def response-validation-failed? - (throws ExceptionInfo #"Response validation failed")) +(defmacro is-response-validation-failed? [form] + `(is (~'thrown? ExceptionInfo #"Response validation failed" ~form))) -(facts "resource definitions" +(deftest resource-definitions-test - (fact "only top-level handler" + (testing "only top-level handler" (let [handler (resource {:handler (constantly (ok {:total 10}))})] - (fact "paths and methods don't matter" - (call handler {:request-method :get, :uri "/"}) => (has-body {:total 10}) - (call handler {:request-method :head, :uri "/kikka"}) => (has-body {:total 10})))) + (testing "paths and methods don't matter" + (is-has-body {:total 10} (call handler {:request-method :get, :uri "/"})) + (is-has-body {:total 10} (call handler {:request-method :head, :uri "/kikka"}))))) - (fact "top-level parameter coercions" + (testing "top-level parameter coercions" (let [handler (resource {:parameters {:query-params {:x Long}} :handler (fnk [[:query-params x]] (ok {:total x}))})] - (call handler {:request-method :get}) => request-validation-failed? - (call handler {:request-method :get, :query-params {:x "1"}}) => (has-body {:total 1}) - (call handler {:request-method :get, :query-params {:x "1", :y "2"}}) => (has-body {:total 1}))) + (is-request-validation-failed? (call handler {:request-method :get})) + (is-has-body {:total 1} (call handler {:request-method :get, :query-params {:x "1"}})) + (is-has-body {:total 1} (call handler {:request-method :get, :query-params {:x "1", :y "2"}})))) - (fact "top-level and operation-level parameter coercions" + (testing "top-level and operation-level parameter coercions" (let [handler (resource {:parameters {:query-params {:x Long}} :get {:parameters {:query-params {(s/optional-key :y) Long}}} :handler (fnk [[:query-params x {y 0}]] (ok {:total (+ x y)}))})] - (call handler {:request-method :get}) => request-validation-failed? - (call handler {:request-method :get, :query-params {:x "1"}}) => (has-body {:total 1}) - (call handler {:request-method :get, :query-params {:x "1", :y "a"}}) => request-validation-failed? - (call handler {:request-method :get, :query-params {:x "1", :y "2"}}) => (has-body {:total 3}) + (is-request-validation-failed? (call handler {:request-method :get})) + (is-has-body {:total 1} (call handler {:request-method :get, :query-params {:x "1"}})) + (is-request-validation-failed? (call handler {:request-method :get, :query-params {:x "1", :y "a"}})) + (is-has-body {:total 3} (call handler {:request-method :get, :query-params {:x "1", :y "2"}})) - (fact "non-matching operation level parameters are not used" - (call handler {:request-method :post, :query-params {:x "1"}}) => (has-body {:total 1}) - (call handler {:request-method :post, :query-params {:x "1", :y "2"}}) => (throws ClassCastException)))) + (testing "non-matching operation level parameters are not used" + (is-has-body {:total 1} (call handler {:request-method :post, :query-params {:x "1"}})) + (is (thrown? ClassCastException (call handler {:request-method :post, :query-params {:x "1", :y "2"}})))))) - (fact "middleware" + (testing "middleware" (let [mw (fn [handler k] (fn [req] (update-in (handler req) [:body :mw] (fnil conj '()) k))) handler (resource {:middleware [[mw :top1] [mw :top2]] @@ -63,13 +62,13 @@ :post {:middleware [[mw :post1] [mw :post2]]} :handler (constantly (ok))})] - (fact "top + method-level mw are applied if they are set" - (call handler {:request-method :get}) => (has-body {:mw [:top1 :top2 :get1 :get2]}) - (call handler {:request-method :post}) => (has-body {:mw [:top1 :top2 :post1 :post2]})) - (fact "top-level mw are applied if method doesn't have mw" - (call handler {:request-method :put}) => (has-body {:mw [:top1 :top2]})))) + (testing "top + method-level mw are applied if they are set" + (is-has-body {:mw [:top1 :top2 :get1 :get2]} (call handler {:request-method :get})) + (is-has-body {:mw [:top1 :top2 :post1 :post2]} (call handler {:request-method :post}))) + (testing "top-level mw are applied if method doesn't have mw" + (is-has-body {:mw [:top1 :top2]} (call handler {:request-method :put}))))) - (fact "operation-level handlers" + (testing "operation-level handlers" (let [handler (resource {:parameters {:query-params {:x Long}} :get {:parameters {:query-params {(s/optional-key :y) Long}} @@ -77,23 +76,23 @@ (ok {:total (+ x y)}))} :post {:parameters {:query-params {:z Long}}}})] - (call handler {:request-method :get}) => request-validation-failed? - (call handler {:request-method :get, :query-params {:x "1"}}) => (has-body {:total 1}) - (call handler {:request-method :get, :query-params {:x "1", :y "a"}}) => request-validation-failed? - (call handler {:request-method :get, :query-params {:x "1", :y "2"}}) => (has-body {:total 3}) + (is-request-validation-failed? (call handler {:request-method :get})) + (is-has-body {:total 1} (call handler {:request-method :get, :query-params {:x "1"}})) + (is-request-validation-failed? (call handler {:request-method :get, :query-params {:x "1", :y "a"}})) + (is-has-body {:total 3} (call handler {:request-method :get, :query-params {:x "1", :y "2"}})) - (fact "if no handler is found, nil is returned" - (call handler {:request-method :post, :query-params {:x "1"}}) => nil))) + (testing "if no handler is found, nil is returned" + (is (nil? (call handler {:request-method :post, :query-params {:x "1"}})))))) - (fact "handler preference" + (testing "handler preference" (let [handler (resource {:get {:handler (constantly (ok {:from "get"}))} :handler (constantly (ok {:from "top"}))})] - (call handler {:request-method :get}) => (has-body {:from "get"}) - (call handler {:request-method :post}) => (has-body {:from "top"}))) + (is-has-body {:from "get"} (call handler {:request-method :get})) + (is-has-body {:from "top"} (call handler {:request-method :post})))) - (fact "resource without coercion" + (testing "resource without coercion" (let [handler (resource {:coercion nil :get {:parameters {:query-params {(s/optional-key :y) Long @@ -102,12 +101,12 @@ (ok {:x x :y y}))}})] - (call handler {:request-method :get}) => (has-body {:x nil, :y nil}) - (call handler {:request-method :get, :query-params {:x "1"}}) => (has-body {:x "1", :y nil}) - (call handler {:request-method :get, :query-params {:x "1", :y "a"}}) => (has-body {:x "1", :y "a"}) - (call handler {:request-method :get, :query-params {:x 1, :y 2}}) => (has-body {:x 1, :y 2}))) - - (fact "parameter mappings" + (is-has-body {:x nil, :y nil} (call handler {:request-method :get})) + (is-has-body {:x "1", :y nil} (call handler {:request-method :get, :query-params {:x "1"}})) + (is-has-body {:x "1", :y "a"} (call handler {:request-method :get, :query-params {:x "1", :y "a"}})) + (is-has-body {:x 1, :y 2} (call handler {:request-method :get, :query-params {:x 1, :y 2}})))) + + (testing "parameter mappings" (let [handler (resource {:get {:parameters {:query-params {:q s/Str} :body-params {:b s/Str} @@ -121,20 +120,21 @@ :header-params :path-params])))}})] - (call handler {:request-method :get - :query-params {:q "q"} - :body-params {:b "b"} - :form-params {:f "f"} - ;; the ring headers - :headers {"h" "h"} - ;; compojure routing - :route-params {:p "p"}}) => (has-body {:query-params {:q "q"} - :body-params {:b "b"} - :form-params {:f "f"} - :header-params {:h "h"} - :path-params {:p "p"}}))) - - (fact "response coercion" + (is-has-body {:query-params {:q "q"} + :body-params {:b "b"} + :form-params {:f "f"} + :header-params {:h "h"} + :path-params {:p "p"}} + (call handler {:request-method :get + :query-params {:q "q"} + :body-params {:b "b"} + :form-params {:f "f"} + ;; the ring headers + :headers {"h" "h"} + ;; compojure routing + :route-params {:p "p"}})))) + + (testing "response coercion" (let [handler (resource {:responses {200 {:schema {:total (s/constrained Long pos? 'pos)}}} :parameters {:query-params {:x Long}} @@ -144,13 +144,13 @@ :handler (fnk [[:query-params x]] (ok {:total x}))})] - (call handler {:request-method :get}) => request-validation-failed? - (call handler {:request-method :get, :query-params {:x "-1"}}) => response-validation-failed? - (call handler {:request-method :get, :query-params {:x "1"}}) => response-validation-failed? - (call handler {:request-method :get, :query-params {:x "10"}}) => (has-body {:total 10}) - (call handler {:request-method :post, :query-params {:x "1"}}) => (has-body {:total 1})))) + (is-request-validation-failed? (call handler {:request-method :get})) + (is-response-validation-failed? (call handler {:request-method :get, :query-params {:x "-1"}})) + (is-response-validation-failed? (call handler {:request-method :get, :query-params {:x "1"}})) + (is-has-body {:total 10} (call handler {:request-method :get, :query-params {:x "10"}})) + (is-has-body {:total 1} (call handler {:request-method :post, :query-params {:x "1"}}))))) -(fact "explicit async tests" +(deftest explicit-async-tests-test (let [handler (resource {:parameters {:query-params {:x Long}} :responses {200 {:schema {:total (s/constrained Long pos? 'pos)}}} @@ -173,44 +173,44 @@ (a/<! (a/timeout 100)) (ok {:total (* x 100)})))}})] - (fact "top-level async handler" + (testing "top-level async handler" (let [respond (promise), res-raise (promise), req-raise (promise)] (handler {:query-params {:x 1}} respond (promise)) (handler {:query-params {:x -1}} (promise) res-raise) (handler {:query-params {:x "x"}} (promise) req-raise) - (deref respond 1000 :timeout) => (has-body {:total 1}) - (throw (deref res-raise 1000 :timeout)) => response-validation-failed? - (throw (deref req-raise 1000 :timeout)) => request-validation-failed?)) + (is-has-body {:total 1} (deref respond 1000 :timeout)) + (is-response-validation-failed? (throw (deref res-raise 1000 :timeout))) + (is-request-validation-failed? (throw (deref req-raise 1000 :timeout))))) - (fact "operation-level async handler" + (testing "operation-level async handler" (let [respond (promise)] (handler {:request-method :get, :query-params {:x 1}} respond (promise)) - (deref respond 1000 :timeout) => (has-body {:total 2}))) + (is-has-body {:total 2} (deref respond 1000 :timeout)))) - (fact "sync handler can be called from async" + (testing "sync handler can be called from async" (let [respond (promise)] (handler {:request-method :post, :query-params {:x 1}} respond (promise)) - (deref respond 1000 :timeout) => (has-body {:total 10})) - (fact "response coercion works" + (is-has-body {:total 10} (deref respond 1000 :timeout))) + (testing "response coercion works" (let [raise (promise)] (handler {:request-method :post, :query-params {:x -1}} (promise) raise) - (throw (deref raise 1000 :timeout)) => response-validation-failed?))) + (is-response-validation-failed? (throw (deref raise 1000 :timeout)))))) - (fact "core.async ManyToManyChannel" - (fact "works with 3-arity" + (testing "core.async ManyToManyChannel" + (testing "works with 3-arity" (let [respond (promise)] (handler {:request-method :put, :query-params {:x 1}} respond (promise)) - (deref respond 2000 :timeout) => (has-body {:total 100})) - (fact "response coercion works" + (is-has-body {:total 100} (deref respond 2000 :timeout))) + (testing "response coercion works" (let [raise (promise)] (handler {:request-method :put, :query-params {:x -1}} (promise) raise) - (throw (deref raise 2000 :timeout)) => response-validation-failed?))) - (fact "fails with 1-arity" - (handler {:request-method :put, :query-params {:x 1}}) => (throws) #_(has-body {:total 100}) - (handler {:request-method :put, :query-params {:x -1}}) => (throws) #_response-validation-failed?)))) + (is-response-validation-failed? (throw (deref raise 2000 :timeout)))))) + (testing "fails with 1-arity" + (is (thrown? Exception (handler {:request-method :put, :query-params {:x 1}}))) #_(is-has-body {:total 100}) + (is (thrown? Exception (handler {:request-method :put, :query-params {:x -1}}))) #_response-validation-failed?)))) -(fact "compojure-api routing integration" +(deftest compojure-api-routing-integration-test (let [app (context "/rest" [] (GET "/no" request @@ -236,34 +236,44 @@ {:get {:handler (fn [request] (ok (select-keys request [:uri :path-info])))}}))] - (fact "normal endpoint works" - (call app {:request-method :get, :uri "/rest/no"}) => (has-body {:uri "/rest/no", :path-info "/no"})) + (testing "normal endpoint works" + (is-has-body + {:uri "/rest/no", :path-info "/no"} + (call app {:request-method :get, :uri "/rest/no"}))) - (fact "wrapped in ANY works" - (call app {:request-method :get, :uri "/rest/any"}) => (has-body "ANY")) + (testing "wrapped in ANY works" + (is-has-body + "ANY" + (call app {:request-method :get, :uri "/rest/any"}))) - (fact "wrapped in context works" - (call app {:request-method :get, :uri "/rest/context"}) => (has-body "CONTEXT")) + (testing "wrapped in context works" + (is-has-body + "CONTEXT" + (call app {:request-method :get, :uri "/rest/context"}))) - (fact "only exact path match works" - (call app {:request-method :get, :uri "/rest/context/2"}) => nil) + (testing "only exact path match works" + (is (nil? (call app {:request-method :get, :uri "/rest/context/2"})))) - (fact "path-parameters work: route-params are left untoucehed, path-params are coerced" - (call app {:request-method :get, :uri "/rest/path/12"}) => (has-body {:path-params {:id 12} - :route-params {:id "12"}})) + (testing "path-parameters work: route-params are left untoucehed, path-params are coerced" + (is-has-body + {:path-params {:id 12} + :route-params {:id "12"}} + (call app {:request-method :get, :uri "/rest/path/12"}))) - (fact "top-level GET without extra path works" - (call app {:request-method :get, :uri "/rest"}) => (has-body {:uri "/rest" - :path-info "/"})) + (testing "top-level GET without extra path works" + (is-has-body + {:uri "/rest" + :path-info "/"} + (call app {:request-method :get, :uri "/rest"}))) - (fact "top-level POST without extra path works" - (call app {:request-method :post, :uri "/rest"}) => nil) + (testing "top-level POST without extra path works" + (is (nil? (call app {:request-method :post, :uri "/rest"})))) - (fact "top-level GET with extra path misses" - (call app {:request-method :get, :uri "/rest/in-peaces"}) => nil))) + (testing "top-level GET with extra path misses" + (is (nil? (call app {:request-method :get, :uri "/rest/in-peaces"})))))) -(fact "swagger-integration" - (fact "explicitely defined methods produce api-docs" +(deftest swagger-integration-test + (testing "explicitly defined methods produce api-docs" (let [app (api (swagger-routes) (context "/rest" [] @@ -276,19 +286,19 @@ :handler (constantly (ok {:total 1}))}))) spec (get-spec app)] - spec => (contains - {:definitions (just - {:Error irrelevant - :Total irrelevant}) - :paths (just - {"/rest" (just - {:get (just - {:parameters (two-of irrelevant) - :responses (just {:200 irrelevant, :400 irrelevant})}) - :post (just - {:parameters (one-of irrelevant) - :responses (just {:400 irrelevant})})})})}))) - (fact "top-level handler doesn't contribute to docs" + (is (= {:definitions #{:Error :Total} + :paths {"/rest" {:get {:parameters 2 + :responses #{:200 :400}} + :post {:parameters 1 + :responses #{:400}}}}} + (-> spec + (select-keys [:definitions :paths]) + (update :definitions (comp set keys)) + (update-in [:paths "/rest" :get :parameters] count) + (update-in [:paths "/rest" :get :responses] (comp set keys)) + (update-in [:paths "/rest" :post :parameters] count) + (update-in [:paths "/rest" :post :responses] (comp set keys))))))) + (testing "top-level handler doesn't contribute to docs" (let [app (api (swagger-routes) (context "/rest" [] @@ -296,5 +306,4 @@ {:handler (constantly (ok {:total 1}))}))) spec (get-spec app)] - spec => (contains - {:paths {}})))) + (is (= {} (:paths spec)))))) diff --git a/test/compojure/api/routes_test.clj b/test/compojure/api/routes_test.clj index 5394103a..dd313f52 100644 --- a/test/compojure/api/routes_test.clj +++ b/test/compojure/api/routes_test.clj @@ -1,5 +1,5 @@ (ns compojure.api.routes-test - (:require [midje.sweet :refer :all] + (:require [clojure.test :refer [deftest is testing]] [compojure.api.sweet :refer :all] [compojure.api.routes :as routes] [ring.util.http-response :refer :all] @@ -10,26 +10,26 @@ (:import (org.joda.time LocalDate) (clojure.lang ExceptionInfo))) -(facts "path-string" +(deftest path-string-test - (fact "missing path parameter" - (#'routes/path-string muuntaja "/api/:kikka" {}) - => (throws IllegalArgumentException)) + (testing "missing path parameter" + (is (thrown? IllegalArgumentException (#'routes/path-string muuntaja "/api/:kikka" {})))) - (fact "missing serialization" - (#'routes/path-string muuntaja "/api/:kikka" {:kikka (reify Comparable)}) - => (throws ExceptionInfo #"Malformed application/json")) + (testing "missing serialization" + (is (thrown-with-msg? + ExceptionInfo #"Malformed application/json" + (#'routes/path-string muuntaja "/api/:kikka" {:kikka (reify Comparable)})))) - (fact "happy path" - (#'routes/path-string muuntaja "/a/:b/:c/d/:e/f" {:b (LocalDate/parse "2015-05-22") - :c 12345 - :e :kikka}) - => "/a/2015-05-22/12345/d/kikka/f")) + (testing "happy path" + (is (= "/a/2015-05-22/12345/d/kikka/f" + (#'routes/path-string muuntaja "/a/:b/:c/d/:e/f" {:b (LocalDate/parse "2015-05-22") + :c 12345 + :e :kikka}))))) -(fact "string-path-parameters" - (#'routes/string-path-parameters "/:foo.json") => {:foo String}) +(deftest string-path-parameters-test + (is (= {:foo String} (#'routes/string-path-parameters "/:foo.json")))) -(facts "nested routes" +(deftest nested-routes-test (let [mw (fn [handler] (fn ([request] (handler request)) ([request raise respond] (handler request raise respond)))) @@ -56,76 +56,74 @@ (swagger-routes) routes)] - (fact "all routes can be invoked" + (testing "all routes can be invoked" (let [[status body] (get* app "/api/v1/hello" {:name "Tommi"})] - status = 200 - body => {:message "Hello, Tommi"}) + (is (= 200 status)) + (is (= body {:message "Hello, Tommi"}))) (let [[status body] (get* app "/api/v1/ping")] - status = 200 - body => {:message "pong - v1"}) + (is (= status 200)) + (is (= body {:message "pong - v1"}))) (let [[status body] (get* app "/api/v2/ping")] - status = 200 - body => {:message "pong - v2"}) + (is (= status 200)) + (is (= body {:message "pong - v2"}))) (let [[status body] (get* app "/api/v3/more")] - status => 200 - body => {:message "v3"})) - - (fact "routes can be extracted at runtime" - (routes/get-routes app) - => [["/swagger.json" :get {:no-doc true - :coercion :schema - :name :compojure.api.swagger/swagger - :public {:x-name :compojure.api.swagger/swagger}}] - ["/api/:version/ping" :get {:coercion :schema - :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] - ["/api/:version/ping" :post {:coercion :schema - :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] - ;; 'ANY' expansion - ["/api/:version/foo" :get {:coercion :schema - :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] - ["/api/:version/foo" :patch {:coercion :schema - :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] - ["/api/:version/foo" :delete {:coercion :schema - :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] - ["/api/:version/foo" :head {:coercion :schema - :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] - ["/api/:version/foo" :post {:coercion :schema - :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] - ["/api/:version/foo" :options {:coercion :schema + (is (= status 200)) + (is (= body {:message "v3"})))) + + (testing "routes can be extracted at runtime" + (is (= [["/swagger.json" :get {:no-doc true + :coercion :schema + :name :compojure.api.swagger/swagger + :public {:x-name :compojure.api.swagger/swagger}}] + ["/api/:version/ping" :get {:coercion :schema + :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] + ["/api/:version/ping" :post {:coercion :schema + :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] + ;; 'ANY' expansion + ["/api/:version/foo" :get {:coercion :schema :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] - ["/api/:version/foo" :put {:coercion :schema - :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] - ;; - ["/api/:version/hello" :get {:coercion :schema - :public {:parameters {:query {:name String, s/Keyword s/Any} - :path {:version String, s/Keyword s/Any}} - :responses {200 {:description "", :schema {:message String}}} - :summary "cool ping"}}] - ["/api/:version/more" :get {:coercion :schema - :public {:parameters {:path {:version String, s/Keyword s/Any}}}}]]) - - (fact "swagger-docs can be generated" - (-> app get-spec :paths keys) - => (just - ["/api/{version}/ping" - "/api/{version}/foo" - "/api/{version}/hello" - "/api/{version}/more"] - :in-any-order)))) + ["/api/:version/foo" :patch {:coercion :schema + :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] + ["/api/:version/foo" :delete {:coercion :schema + :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] + ["/api/:version/foo" :head {:coercion :schema + :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] + ["/api/:version/foo" :post {:coercion :schema + :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] + ["/api/:version/foo" :options {:coercion :schema + :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] + ["/api/:version/foo" :put {:coercion :schema + :public {:parameters {:path {:version String, s/Keyword s/Any}}}}] + ;; + ["/api/:version/hello" :get {:coercion :schema + :public {:parameters {:query {:name String, s/Keyword s/Any} + :path {:version String, s/Keyword s/Any}} + :responses {200 {:description "", :schema {:message String}}} + :summary "cool ping"}}] + ["/api/:version/more" :get {:coercion :schema + :public {:parameters {:path {:version String, s/Keyword s/Any}}}}]] + (routes/get-routes app)))) + + (testing "swagger-docs can be generated" + (is (= (sort ["/api/{version}/ping" + "/api/{version}/foo" + "/api/{version}/hello" + "/api/{version}/more"]) + (-> app get-spec :paths keys sort)))))) (def more-routes (routes (GET "/more" [] (ok {:gary "moore"})))) -(facts "following var-routes, #219" +(deftest issue-219-test ;"following var-routes, #219" (let [routes (context "/api" [] #'more-routes)] - (routes/get-routes routes) => [["/api/more" :get {:static-context? true}]])) + (is (= (routes/get-routes routes) [["/api/more" :get {:static-context? true}]])))) -(facts "dynamic routes" +(deftest dynamic-routes-test (let [more-routes (fn [version] (GET (str "/" version) [] (ok {:message version}))) @@ -136,72 +134,74 @@ (swagger-routes) routes)] - (fact "all routes can be invoked" + (testing "all routes can be invoked" (let [[status body] (get* app "/api/v3/v3")] - status => 200 - body => {:message "v3"}) + (is (= status 200)) + (is (= body {:message "v3"}))) (let [[status body] (get* app "/api/v6/v6")] - status => 200 - body => {:message "v6"})) - - (fact "routes can be extracted at runtime" - (routes/get-routes app) - => [["/swagger.json" :get {:no-doc true, - :coercion :schema - :name :compojure.api.swagger/swagger - :public {:x-name :compojure.api.swagger/swagger}}] - ["/api/:version/[]" :get {:coercion :schema - :public {:parameters {:path {:version String, s/Keyword s/Any}}}}]]) - - (fact "swagger-docs can be generated" - (-> app get-spec :paths keys) - => ["/api/{version}/[]"]))) - -(fact "route merging" - (routes/get-routes (routes (routes))) => [] - (routes/get-routes (routes (swagger-routes {:spec nil}))) => [] - (routes/get-routes (routes (routes (GET "/ping" [] "pong")))) => [["/ping" :get {}]]) - -(fact "invalid route options" + (is (= status 200)) + (is (= body {:message "v6"})))) + + (testing "routes can be extracted at runtime" + (is (= (routes/get-routes app) + [["/swagger.json" :get {:no-doc true, + :coercion :schema + :name :compojure.api.swagger/swagger + :public {:x-name :compojure.api.swagger/swagger}}] + ["/api/:version/[]" :get {:coercion :schema + :public {:parameters {:path {:version String, s/Keyword s/Any}}}}]]))) + + (testing "swagger-docs can be generated" + (is (= (-> app get-spec :paths keys) + ["/api/{version}/[]"]))))) + +(deftest route-merging-test + (is (= (routes/get-routes (routes (routes))) [])) + (is (= (routes/get-routes (routes (swagger-routes {:spec nil}))) [])) + (is (= (routes/get-routes (routes (routes (GET "/ping" [] "pong")))) [["/ping" :get {}]]))) + +(deftest invalid-route-options-test (let [r (routes (constantly nil))] - (fact "ignore 'em all" - (routes/get-routes r) => [] - (routes/get-routes r nil) => [] - (routes/get-routes r {:invalid-routes-fn nil}) => []) + (testing "ignore 'em all" + (is (= (routes/get-routes r) [])) + (is (= (routes/get-routes r nil) [])) + (is (= (routes/get-routes r {:invalid-routes-fn nil}) []))) - (fact "log warnings" - (routes/get-routes r {:invalid-routes-fn routes/log-invalid-child-routes}) => [] - (provided - (compojure.api.impl.logging/log! :warn irrelevant) => irrelevant :times 1)) + (testing "log warnings" + (let [a (atom [])] + (with-redefs [compojure.api.impl.logging/log! (fn [& args] (swap! a conj args))] + (is (= [] (routes/get-routes r {:invalid-routes-fn routes/log-invalid-child-routes})))) + (is (= 1 (count @a))))) - (fact "throw exception" - (routes/get-routes r {:invalid-routes-fn routes/fail-on-invalid-child-routes})) => throws)) + (testing "throw exception" + (is (thrown? Exception (routes/get-routes r {:invalid-routes-fn routes/fail-on-invalid-child-routes})))))) -(fact "context routes with compojure destructuring" +(deftest context-routes-with-compojure-destructuring-test (let [app (context "/api" req (GET "/ping" [] (ok (:magic req))))] - (app {:request-method :get :uri "/api/ping" :magic {:just "works"}}) => (contains {:body {:just "works"}}))) + (is (= {:just "works"} + (:body (app {:request-method :get :uri "/api/ping" :magic {:just "works"}})))))) -(fact "dynamic context routes" +(deftest dynamic-context-routes-test (let [endpoint? (atom true) app (context "/api" [] :dynamic true (when @endpoint? (GET "/ping" [] (ok "pong"))))] - (fact "the endpoint exists" - (app {:request-method :get :uri "/api/ping"}) => (contains {:body "pong"})) + (testing "the endpoint exists" + (is (= (:body (app {:request-method :get :uri "/api/ping"})) "pong"))) (reset! endpoint? false) - (fact "the endpoint does not exist" - (app {:request-method :get :uri "/api/ping"}) => nil))) + (testing "the endpoint does not exist" + (is (= (app {:request-method :get :uri "/api/ping"}) nil))))) -(fact "listing static context routes" +(deftest listing-static-context-routes-test (let [app (routes (context "/static" [] (GET "/ping" [] (ok "pong"))) (context "/dynamic" req (GET "/ping" [] (ok "pong"))))] - (routes/get-static-context-routes app) - => [["/static/ping" :get {:static-context? true}]])) + (is (= (routes/get-static-context-routes app) + [["/static/ping" :get {:static-context? true}]])))) diff --git a/test/compojure/api/swagger_ordering_test.clj b/test/compojure/api/swagger_ordering_test.clj index 55112ecf..62c8bf69 100644 --- a/test/compojure/api/swagger_ordering_test.clj +++ b/test/compojure/api/swagger_ordering_test.clj @@ -1,5 +1,5 @@ (ns compojure.api.swagger-ordering-test - (:require [midje.sweet :refer :all] + (:require [clojure.test :refer [deftest is testing]] [compojure.api.sweet :refer :all] [compojure.api.test-utils :refer :all])) @@ -9,7 +9,7 @@ (GET "/7" [] identity) (GET "/8" [] identity))) -(facts "with 10+ routes" +(deftest with-10+-routes-test (let [app (api (context "/a" [] (GET "/1" [] identity) @@ -23,14 +23,15 @@ (GET "/9" [] identity) (GET "/10" [] identity))))] - (fact "swagger-api order is maintained" - (keys (extract-paths app)) => ["/a/1" - "/a/2" - "/a/3" - "/a/b/4" - "/a/b/5" - "/a/c/6" - "/a/c/7" - "/a/c/8" - "/a/c/9" - "/a/c/10"]))) + (testing "swagger-api order is maintained" + (is (= (keys (extract-paths app)) + ["/a/1" + "/a/2" + "/a/3" + "/a/b/4" + "/a/b/5" + "/a/c/6" + "/a/c/7" + "/a/c/8" + "/a/c/9" + "/a/c/10"]))))) diff --git a/test/compojure/api/swagger_test.clj b/test/compojure/api/swagger_test.clj index 9269de93..8e9c73ff 100644 --- a/test/compojure/api/swagger_test.clj +++ b/test/compojure/api/swagger_test.clj @@ -4,14 +4,14 @@ [compojure.api.swagger :as swagger] compojure.core [compojure.api.test-utils :refer :all] - [midje.sweet :refer :all])) + [clojure.test :refer [deftest is testing]])) (defmacro optional-routes [p & body] (when p `(routes ~@body))) (defmacro GET+ [p & body] `(GET ~(str "/xxx" p) ~@body)) -(fact "extracting compojure paths" +(deftest extracting-compojure-paths-test - (fact "all compojure.api.core macros are interpreted" + (testing "all compojure.api.core macros are interpreted" (let [app (context "/a" [] (routes (context "/b" a @@ -25,91 +25,94 @@ (context "/:i/:j" [] (GET "/k/:l/m/:n" [] identity))))] - (extract-paths app) - #_#_ - => {"/a/b/c" {:get {}} - "/a/b/d" {:post {}} - "/a/b/e" {:put {}} - "/a/b/f" {:delete {}} - "/a/b/g" {:options {}} - "/a/b/h" {:patch {}} - "/a/:i/:j/k/:l/m/:n" {:get {:parameters {:path {:i String - :j String - :l String - :n String}}}}})) - - (fact "runtime code in route is NOT ignored" - (extract-paths - (context "/api" [] - (if false - (GET "/true" [] identity) - (PUT "/false" [] identity)))) => {"/api/false" {:put {}}}) - - (fact "route-macros are expanded" - (extract-paths - (context "/api" [] - (optional-routes true (GET "/true" [] identity)) - (optional-routes false (PUT "/false" [] identity)))) => {"/api/true" {:get {}}}) - - (fact "endpoint-macros are expanded" - (extract-paths - (context "/api" [] - (GET+ "/true" [] identity))) => {"/api/xxx/true" {:get {}}}) - - (fact "Vanilla Compojure defroutes are NOT followed" + (is (= (extract-paths app) + {"/a/b/c" {:get {}} + "/a/b/d" {:post {}} + "/a/b/e" {:put {}} + "/a/b/f" {:delete {}} + "/a/b/g" {:options {}} + "/a/b/h" {:patch {}} + "/a/:i/:j/k/:l/m/:n" {:get {:parameters {:path {:i String + :j String + :l String + :n String}}}}})))) + + (testing "runtime code in route is NOT ignored" + (is (= (extract-paths + (context "/api" [] + (if false + (GET "/true" [] identity) + (PUT "/false" [] identity)))) + {"/api/false" {:put {}}}))) + + (testing "route-macros are expanded" + (is (= (extract-paths + (context "/api" [] + (optional-routes true (GET "/true" [] identity)) + (optional-routes false (PUT "/false" [] identity)))) + {"/api/true" {:get {}}}))) + + (testing "endpoint-macros are expanded" + (is (= (extract-paths + (context "/api" [] + (GET+ "/true" [] identity))) + {"/api/xxx/true" {:get {}}}))) + + (testing "Vanilla Compojure defroutes are NOT followed" (compojure.core/defroutes even-more-routes (GET "/even" [] identity)) (compojure.core/defroutes more-routes (context "/more" [] even-more-routes)) - (extract-paths - (context "/api" [] - (GET "/true" [] identity) - more-routes)) => {"/api/true" {:get {}}}) + (is (= (extract-paths + (context "/api" [] + (GET "/true" [] identity) + more-routes)) + {"/api/true" {:get {}}}))) - (fact "Compojure Api defroutes and def routes are followed" + (testing "Compojure Api defroutes and def routes are followed" (def even-more-routes (GET "/even" [] identity)) (defroutes more-routes (context "/more" [] even-more-routes)) - (extract-paths - (context "/api" [] - (GET "/true" [] identity) - more-routes)) => {"/api/true" {:get {}} - "/api/more/even" {:get {}}}) - - (fact "Parameter regular expressions are discarded" - (extract-paths - (context "/api" [] - (GET ["/:param" :param #"[a-z]+"] [] identity))) - - => {"/api/:param" {:get {:parameters {:path {:param String}}}}})) - -(fact "context meta-data" - (extract-paths - (context "/api/:id" [] - :summary "top-summary" - :path-params [id :- String] - :tags [:kiss] - (GET "/kikka" [] - identity) - (context "/ipa" [] - :summary "mid-summary" - :tags [:wasp] - (GET "/kukka/:kukka" [] - :summary "bottom-summary" - :path-params [kukka :- String] - :tags [:venom]) - (GET "/kakka" [] - identity)))) - - => {"/api/:id/kikka" {:get {:summary "top-summary" - :tags #{:kiss} - :parameters {:path {:id String}}}} - "/api/:id/ipa/kukka/:kukka" {:get {:summary "bottom-summary" - :tags #{:venom} - :parameters {:path {:id String - :kukka String}}}} - "/api/:id/ipa/kakka" {:get {:summary "mid-summary" - :tags #{:wasp} - :parameters {:path {:id String}}}}}) - -(facts "duplicate context merge" + (is (= (extract-paths + (context "/api" [] + (GET "/true" [] identity) + more-routes)) + {"/api/true" {:get {}} + "/api/more/even" {:get {}}}))) + + (testing "Parameter regular expressions are discarded" + (is (= (extract-paths + (context "/api" [] + (GET ["/:param" :param #"[a-z]+"] [] identity))) + {"/api/:param" {:get {:parameters {:path {:param String}}}}})))) + +(deftest context-meta-data-test-1 + (is (= (extract-paths + (context "/api/:id" [] + :summary "top-summary" + :path-params [id :- String] + :tags [:kiss] + (GET "/kikka" [] + identity) + (context "/ipa" [] + :summary "mid-summary" + :tags [:wasp] + (GET "/kukka/:kukka" [] + :summary "bottom-summary" + :path-params [kukka :- String] + :tags [:venom]) + (GET "/kakka" [] + identity)))) + + {"/api/:id/kikka" {:get {:summary "top-summary" + :tags #{:kiss} + :parameters {:path {:id String}}}} + "/api/:id/ipa/kukka/:kukka" {:get {:summary "bottom-summary" + :tags #{:venom} + :parameters {:path {:id String + :kukka String}}}} + "/api/:id/ipa/kakka" {:get {:summary "mid-summary" + :tags #{:wasp} + :parameters {:path {:id String}}}}}))) + +(deftest duplicate-context-merge-test (let [app (routes (context "/api" [] :tags [:kiss] @@ -119,9 +122,9 @@ :tags [:kiss] (GET "/kukka" [] identity)))] - (extract-paths app) - => {"/api/kukka" {:get {:tags #{:kiss}}} - "/api/kakka" {:get {:tags #{:kiss}}}})) + (is (= (extract-paths app) + {"/api/kukka" {:get {:tags #{:kiss}}} + "/api/kakka" {:get {:tags #{:kiss}}}})))) (def r1 (GET "/:id" [] @@ -132,64 +135,66 @@ :path-params [id :- Long] identity)) -(facts "defined routes path-params" - (extract-paths (routes r1 r2)) - => {"/:id" {:get {:parameters {:path {:id String}}}} - "/kukka/:id" {:get {:parameters {:path {:id Long}}}}}) - -(fact "context meta-data" - (extract-paths - (context "/api/:id" [] - :summary "top-summary" - :path-params [id :- String] - :tags [:kiss] - (GET "/kikka" [] - identity) - (context "/ipa" [] - :summary "mid-summary" - :tags [:wasp] - (GET "/kukka/:kukka" [] - :summary "bottom-summary" - :path-params [kukka :- String] - :tags [:venom]) - (GET "/kakka" [] - identity)))) - - => {"/api/:id/kikka" {:get {:summary "top-summary" - :tags #{:kiss} - :parameters {:path {:id String}}}} - "/api/:id/ipa/kukka/:kukka" {:get {:summary "bottom-summary" - :tags #{:venom} - :parameters {:path {:id String - :kukka String}}}} - "/api/:id/ipa/kakka" {:get {:summary "mid-summary" - :tags #{:wasp} - :parameters {:path {:id String}}}}}) - -(fact "path params followed by an extension" - (extract-paths - (GET "/:foo.json" [] - :path-params [foo :- String] - identity)) - => {"/:foo.json" {:get {:parameters {:path {:foo String}}}}}) - -(facts - (tabular - (fact "swagger-routes basePath can be changed" - (let [app (api (swagger-routes ?given-options))] - (-> - (get* app "/swagger.json") - (nth 1) - :basePath) - => ?expected-base-path - (nth (raw-get* app "/conf.js") 1) => (str "window.API_CONF = {\"url\":\"" ?expected-swagger-docs-path "\"};"))) - ?given-options ?expected-swagger-docs-path ?expected-base-path - {} "/swagger.json" "/" - {:data {:basePath "/app"}} "/app/swagger.json" "/app" - {:data {:basePath "/app"} :options {:ui {:swagger-docs "/imaginary.json"}}} "/imaginary.json" "/app")) - -(fact "change of contract in 1.2.0 with swagger-docs % swagger-ui" - (fact "swagger-ui" - (swagger/swagger-ui "/path") => (throws AssertionError)) - (fact "swagger-docs" - (swagger/swagger-docs "/path") => (throws AssertionError))) +(deftest defined-routes-path-params-test + (is (= (extract-paths (routes r1 r2)) + {"/:id" {:get {:parameters {:path {:id String}}}} + "/kukka/:id" {:get {:parameters {:path {:id Long}}}}}))) + +;;FIXME is this a duplicate of context-meta-data-test-1? +(deftest context-meta-data-test-2 + (is (= (extract-paths + (context "/api/:id" [] + :summary "top-summary" + :path-params [id :- String] + :tags [:kiss] + (GET "/kikka" [] + identity) + (context "/ipa" [] + :summary "mid-summary" + :tags [:wasp] + (GET "/kukka/:kukka" [] + :summary "bottom-summary" + :path-params [kukka :- String] + :tags [:venom]) + (GET "/kakka" [] + identity)))) + + {"/api/:id/kikka" {:get {:summary "top-summary" + :tags #{:kiss} + :parameters {:path {:id String}}}} + "/api/:id/ipa/kukka/:kukka" {:get {:summary "bottom-summary" + :tags #{:venom} + :parameters {:path {:id String + :kukka String}}}} + "/api/:id/ipa/kakka" {:get {:summary "mid-summary" + :tags #{:wasp} + :parameters {:path {:id String}}}}}))) + +(deftest path-params-followed-by-an-extension-test + (is (= (extract-paths + (GET "/:foo.json" [] + :path-params [foo :- String] + identity)) + {"/:foo.json" {:get {:parameters {:path {:foo String}}}}}))) + +(deftest swagger-routes-basePath-test + (testing "swagger-routes basePath can be changed" + (doseq [[?given-options ?expected-swagger-docs-path ?expected-base-path :as test-case] + [[{} "/swagger.json" "/" {:data {:basePath "/app"}} "/app/swagger.json" "/app"] + [{:data {:basePath "/app"} :options {:ui {:swagger-docs "/imaginary.json"}}} "/imaginary.json" "/app"]]] + (testing (pr-str test-case) + (let [app (api (swagger-routes ?given-options))] + (is (= (-> + (get* app "/swagger.json") + (nth 1) + :basePath) + ?expected-base-path)) + (is (= (nth (raw-get* app "/conf.js") 1) + (str "window.API_CONF = {\"url\":\"" ?expected-swagger-docs-path "\"};")))))))) + +;;"change of contract in 1.2.0 with swagger-docs % swagger-ui" +(deftest change-1-2-0-swagger-docs-ui-test + (testing "swagger-ui" + (is (thrown? AssertionError (swagger/swagger-ui "/path")))) + (testing "swagger-docs" + (is (thrown? AssertionError (swagger/swagger-docs "/path"))))) diff --git a/test/compojure/api/sweet_test.clj b/test/compojure/api/sweet_test.clj index c2082772..c6079843 100644 --- a/test/compojure/api/sweet_test.clj +++ b/test/compojure/api/sweet_test.clj @@ -1,7 +1,7 @@ (ns compojure.api.sweet-test (:require [compojure.api.sweet :refer :all] [compojure.api.test-utils :refer :all] - [midje.sweet :refer :all] + [clojure.test :refer [deftest is testing]] [ring.mock.request :refer :all] [schema.core :as s] [ring.swagger.validator :as v])) @@ -65,124 +65,135 @@ :return [String] identity)))) -(facts "api documentation" - (fact "details are generated" +(deftest api-documentation-test + (testing "details are generated" - (extract-paths app) + (is (= (extract-paths app) - => {"/swagger.json" {:get {:x-name :compojure.api.swagger/swagger}} - "/ping" {:get {}} - "/api/ping" {:get {}} - "/api/bands" {:get {:x-name :bands - :operationId "getBands" - :description "bands bands bands" - :responses {200 {:schema [Band] - :description ""}} - :summary "Gets all Bands"} - :post {:operationId "addBand" - :parameters {:body [NewBand]} - :responses {200 {:schema Band - :description ""}} - :summary "Adds a Band"}} - "/api/bands/:id" {:get {:operationId "getBand" - :responses {200 {:schema Band + {"/swagger.json" {:get {:x-name :compojure.api.swagger/swagger}} + "/ping" {:get {}} + "/api/ping" {:get {}} + "/api/bands" {:get {:x-name :bands + :operationId "getBands" + :description "bands bands bands" + :responses {200 {:schema [Band] :description ""}} - :summary "Gets a Band" - :parameters {:path {:id String}}}} - "/api/query" {:get {:parameters {:query {:qp Boolean - s/Keyword s/Any}}}} - "/api/header" {:get {:parameters {:header {:hp Boolean - s/Keyword s/Any}}}} - "/api/form" {:post {:parameters {:formData {:fp Boolean}} - :consumes ["application/x-www-form-urlencoded"]}} - "/api/primitive" {:get {:responses {200 {:schema String - :description ""}}}} - "/api/primitiveArray" {:get {:responses {200 {:schema [String] - :description ""}}}}}) + :summary "Gets all Bands"} + :post {:operationId "addBand" + :parameters {:body [NewBand]} + :responses {200 {:schema Band + :description ""}} + :summary "Adds a Band"}} + "/api/bands/:id" {:get {:operationId "getBand" + :responses {200 {:schema Band + :description ""}} + :summary "Gets a Band" + :parameters {:path {:id String}}}} + "/api/query" {:get {:parameters {:query {:qp Boolean + s/Keyword s/Any}}}} + "/api/header" {:get {:parameters {:header {:hp Boolean + s/Keyword s/Any}}}} + "/api/form" {:post {:parameters {:formData {:fp Boolean}} + :consumes ["application/x-www-form-urlencoded"]}} + "/api/primitive" {:get {:responses {200 {:schema String + :description ""}}}} + "/api/primitiveArray" {:get {:responses {200 {:schema [String] + :description ""}}}}}))) - (fact "api-listing works" + (testing "api-listing works" (let [spec (get-spec app)] - spec => (just - {:swagger "2.0" - :info {:version "1.0.0" - :title "Sausages" - :description "Sausage description" - :termsOfService "http://helloreverb.com/terms/" - :contact {:name "My API Team" - :email "foo@example.com" - :url "http://www.metosin.fi"} - :license {:name "Eclipse Public License" - :url "http://www.eclipse.org/legal/epl-v10.html"}} - :basePath "/" - :consumes (just - ["application/json" - "application/edn" - "application/transit+json" - "application/transit+msgpack"] - :in-any-order), - :produces (just - ["application/json" - "application/edn" - "application/transit+json" - "application/transit+msgpack"] - :in-any-order) - :paths {"/api/bands" {:get {:x-name "bands" - :operationId "getBands" - :description "bands bands bands" - :responses {:200 {:description "" - :schema {:items {:$ref "#/definitions/Band"} - :type "array"}}} - :summary "Gets all Bands"} - :post {:operationId "addBand" - :parameters [{:description "" - :in "body" - :name "NewBand" - :required true - :schema {:items {:$ref "#/definitions/NewBand"} - :type "array"}}] - :responses {:200 {:description "" - :schema {:$ref "#/definitions/Band"}}} - :summary "Adds a Band"}} - "/api/bands/{id}" {:get {:operationId "getBand" - :parameters [{:description "" - :in "path" - :name "id" - :required true - :type "string"}] - :responses {:200 {:description "" - :schema {:$ref "#/definitions/Band"}}} - :summary "Gets a Band"}} - "/api/query" {:get {:parameters [{:in "query" - :name "qp" - :description "" - :required true - :type "boolean"}] - :responses {:default {:description ""}}}} - "/api/header" {:get {:parameters [{:in "header" - :name "hp" - :description "" - :required true - :type "boolean"}] - :responses {:default {:description ""}}}} - "/api/form" {:post {:parameters [{:in "formData" - :name "fp" - :description "" - :required true - :type "boolean"}] - :responses {:default {:description ""}} - :consumes ["application/x-www-form-urlencoded"]}} - "/api/ping" {:get {:responses {:default {:description ""}}}} - "/api/primitive" {:get {:responses {:200 {:description "" - :schema {:type "string"}}}}} - "/api/primitiveArray" {:get {:responses {:200 {:description "" - :schema {:items {:type "string"} - :type "array"}}}}} - "/ping" {:get {:responses {:default {:description ""}}}}} - :definitions {:Band {:type "object" + (is (= (-> spec + (update :consumes sort) + (update :produces sort)) + {:swagger "2.0" + :info {:version "1.0.0" + :title "Sausages" + :description "Sausage description" + :termsOfService "http://helloreverb.com/terms/" + :contact {:name "My API Team" + :email "foo@example.com" + :url "http://www.metosin.fi"} + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"}} + :basePath "/" + :consumes (sort ["application/json" + "application/edn" + "application/transit+json" + "application/transit+msgpack"]), + :produces (sort ["application/json" + "application/edn" + "application/transit+json" + "application/transit+msgpack"]) + :paths {"/api/bands" {:get {:x-name "bands" + :operationId "getBands" + :description "bands bands bands" + :responses {:200 {:description "" + :schema {:items {:$ref "#/definitions/Band"} + :type "array"}}} + :summary "Gets all Bands"} + :post {:operationId "addBand" + :parameters [{:description "" + :in "body" + :name "NewBand" + :required true + :schema {:items {:$ref "#/definitions/NewBand"} + :type "array"}}] + :responses {:200 {:description "" + :schema {:$ref "#/definitions/Band"}}} + :summary "Adds a Band"}} + "/api/bands/{id}" {:get {:operationId "getBand" + :parameters [{:description "" + :in "path" + :name "id" + :required true + :type "string"}] + :responses {:200 {:description "" + :schema {:$ref "#/definitions/Band"}}} + :summary "Gets a Band"}} + "/api/query" {:get {:parameters [{:in "query" + :name "qp" + :description "" + :required true + :type "boolean"}] + :responses {:default {:description ""}}}} + "/api/header" {:get {:parameters [{:in "header" + :name "hp" + :description "" + :required true + :type "boolean"}] + :responses {:default {:description ""}}}} + "/api/form" {:post {:parameters [{:in "formData" + :name "fp" + :description "" + :required true + :type "boolean"}] + :responses {:default {:description ""}} + :consumes ["application/x-www-form-urlencoded"]}} + "/api/ping" {:get {:responses {:default {:description ""}}}} + "/api/primitive" {:get {:responses {:200 {:description "" + :schema {:type "string"}}}}} + "/api/primitiveArray" {:get {:responses {:200 {:description "" + :schema {:items {:type "string"} + :type "array"}}}}} + "/ping" {:get {:responses {:default {:description ""}}}}} + :definitions {:Band {:type "object" + :properties {:description {:type "string" + :x-nullable true} + :id {:format "int64", :type "integer"} + :name {:type "string"} + :toppings {:items {:enum ["olives" + "pepperoni" + "ham" + "cheese" + "habanero"] + :type "string"} + :type "array"}} + :required ["id" "name" "toppings"] + :additionalProperties false} + :NewBand {:type "object" :properties {:description {:type "string" :x-nullable true} - :id {:format "int64", :type "integer"} :name {:type "string"} :toppings {:items {:enum ["olives" "pepperoni" @@ -191,37 +202,25 @@ "habanero"] :type "string"} :type "array"}} - :required ["id" "name" "toppings"] - :additionalProperties false} - :NewBand {:type "object" - :properties {:description {:type "string" - :x-nullable true} - :name {:type "string"} - :toppings {:items {:enum ["olives" - "pepperoni" - "ham" - "cheese" - "habanero"] - :type "string"} - :type "array"}} - :required ["name" "toppings"] - :additionalProperties false}}}) + :required ["name" "toppings"] + :additionalProperties false}}})) - (fact "spec is valid" - (v/validate spec) => nil)))) + (testing "spec is valid" + (is (= (v/validate spec) nil)))))) -(fact "produces & consumes" +(deftest produces-and-consumes-test (let [app (api {:swagger {:spec "/swagger.json" :data {:produces ["application/json" "application/edn"] :consumes ["application/json" "application/edn"]}}} ping-route)] - (get-spec app) => (contains - {:consumes (just - ["application/json" - "application/edn"] - :in-any-order) - :produces (just - ["application/json" - "application/edn"] - :in-any-order)}))) + (is (= (-> (get-spec app) + (select-keys [:consumes :produces]) + (update :consumes sort) + (update :produces sort)) + {:consumes (sort + ["application/json" + "application/edn"]) + :produces (sort + ["application/json" + "application/edn"])})))) diff --git a/test19/compojure/api/coercion/issue336_test.clj b/test19/compojure/api/coercion/issue336_test.clj index 07e390fc..32b6251b 100644 --- a/test19/compojure/api/coercion/issue336_test.clj +++ b/test19/compojure/api/coercion/issue336_test.clj @@ -1,5 +1,5 @@ (ns compojure.api.coercion.issue336-test - (:require [midje.sweet :refer :all] + (:require [clojure.test :refer [deftest is testing]] [compojure.api.test-utils :refer :all] [ring.util.http-response :refer :all] [compojure.api.sweet :refer :all] @@ -49,10 +49,10 @@ :handler (fn [{:keys [query-params]}] (ok query-params))}}))))) -(fact "coercion works with s/and" +(deftest coercion-works-with-s-and-test (let [data {:endpoint "http://sushi.cambridge.org/GetReport" :customer-id "abc" :requestor-id "abc"} [status body] (get* app "/api/jr1" data)] - status => 200 - body => data)) + (is (= status 200)) + (is (= body data)))) diff --git a/test19/compojure/api/coercion/spec_coercion_explain_test.clj b/test19/compojure/api/coercion/spec_coercion_explain_test.clj index fc2d6f2a..680d40cd 100644 --- a/test19/compojure/api/coercion/spec_coercion_explain_test.clj +++ b/test19/compojure/api/coercion/spec_coercion_explain_test.clj @@ -1,5 +1,5 @@ (ns compojure.api.coercion.spec-coercion-explain-test - (:require [midje.sweet :refer :all] + (:require [clojure.test :refer [deftest is testing]] [clojure.spec.alpha :as s] [spec-tools.spec :as spec] [compojure.api.test-utils :refer :all] @@ -25,16 +25,18 @@ (def coerced-value {:name "foo" :age "24" :languages #{:clj} :birthdate #inst "1968-01-02T15:04:05Z"}) (def invalid-value {:name "foo" :age "24" :lanxguages ["clj"] :birthdate "1968-01-02T15:04:05Z"}) -(fact "request-coercion" +(deftest request-coercion-test (let [c! #(coercion/coerce-request! ::spec :body-params :body false false %)] - (fact "default coercion" - (c! {:body-params valid-value - :muuntaja/request {:format "application/json"} - ::request/coercion :spec}) => coerced-value - (c! {:body-params invalid-value - :muuntaja/request {:format "application/json"} - ::request/coercion :spec}) => (throws) + (testing "default coercion" + (is (= (c! {:body-params valid-value + :muuntaja/request {:format "application/json"} + ::request/coercion :spec}) + coerced-value)) + (is (thrown? Exception + (c! {:body-params invalid-value + :muuntaja/request {:format "application/json"} + ::request/coercion :spec}))) (try (c! {:body-params invalid-value :muuntaja/request {:format "application/json"} @@ -42,12 +44,12 @@ (catch Exception e (let [data (ex-data e) spec-problems (get-in data [:problems ::s/problems])] - (count spec-problems) => 1 - (first spec-problems) => (contains {:in [] - :path [] - :val {:age "24" - :birthdate #inst "1968-01-02T15:04:05Z" - :name "foo"} - :via [::spec]}))))))) - - + (is (= (count spec-problems) 1)) + (is (= (select-keys (first spec-problems) + [:in :path :val :via]) + {:in [] + :path [] + :val {:age "24" + :birthdate #inst "1968-01-02T15:04:05Z" + :name "foo"} + :via [::spec]})))))))) diff --git a/test19/compojure/api/coercion/spec_coercion_test.clj b/test19/compojure/api/coercion/spec_coercion_test.clj index 2d982df1..d4961679 100644 --- a/test19/compojure/api/coercion/spec_coercion_test.clj +++ b/test19/compojure/api/coercion/spec_coercion_test.clj @@ -1,5 +1,6 @@ (ns compojure.api.coercion.spec-coercion-test - (:require [midje.sweet :refer :all] + (:require [clojure.test :refer [deftest is testing]] + expound.alpha [clojure.spec.alpha :as s] [compojure.api.test-utils :refer :all] [compojure.api.sweet :refer :all] @@ -42,62 +43,74 @@ (def valid-value {:kikka :kukka}) (def invalid-value {:kikka "kukka"}) -(fact "request-coercion" +(deftest request-coercion-test (let [c! #(coercion/coerce-request! ::spec :body-params :body false false %)] - (fact "default coercion" - (c! {:body-params valid-value - ::request/coercion :spec}) => valid-value - (c! {:body-params invalid-value - ::request/coercion :spec}) => (throws) + (testing "default coercion" + (is (= (c! {:body-params valid-value + ::request/coercion :spec}) + valid-value)) + (is (thrown? Exception + (c! {:body-params invalid-value + ::request/coercion :spec}))) (try (c! {:body-params invalid-value ::request/coercion :spec}) (catch Exception e - (ex-data e) => (contains - {:type :compojure.api.exception/request-validation - :coercion (coercion/resolve-coercion :spec) - :in [:request :body-params] - :spec st/spec? - :value invalid-value - :problems (just {::s/problems [{:in [:kikka] - :path [:kikka] - :pred `keyword? - :val "kukka" - :via [::spec ::kikka]}] - ::s/spec st/spec? - ::s/value invalid-value}) - :request (contains {:body-params {:kikka "kukka"}})})))) - - (fact "coercion also unforms" + (is (= (-> (ex-data e) + (select-keys [:type :coercion :in :spec :value :problems :request]) + (update :request select-keys [:body-params]) + (update :spec (comp boolean st/spec?)) + (update-in [:problems ::s/spec] (comp boolean st/spec?))) + {:type :compojure.api.exception/request-validation + :coercion (coercion/resolve-coercion :spec) + :in [:request :body-params] + :spec true + :value invalid-value + :problems {::s/problems [{:in [:kikka] + :path [:kikka] + :pred `keyword? + :val "kukka" + :via [::spec ::kikka]}] + ::s/spec true + ::s/value invalid-value} + :request {:body-params {:kikka "kukka"}}}))))) + + (testing "coercion also unforms" (let [spec (s/or :int int? :keyword keyword?) c! #(coercion/coerce-request! spec :body-params :body false false %)] - (c! {:body-params 1 - ::request/coercion :spec}) => 1 - (c! {:body-params :kikka - ::request/coercion :spec}) => :kikka)) - - (fact "format-based coercion" - (c! {:body-params valid-value - ::request/coercion :spec - :muuntaja/request {:format "application/json"}}) => valid-value - (c! {:body-params invalid-value - ::request/coercion :spec - :muuntaja/request {:format "application/json"}}) => valid-value) - - (fact "no coercion" - (c! {:body-params valid-value - ::request/coercion nil - :muuntaja/request {:format "application/json"}}) => valid-value - (c! {:body-params invalid-value - ::request/coercion nil - :muuntaja/request {:format "application/json"}}) => invalid-value))) + (is (= (c! {:body-params 1 + ::request/coercion :spec}) + 1)) + (is (= (c! {:body-params :kikka + ::request/coercion :spec}) + :kikka)))) + + (testing "format-based coercion" + (is (= (c! {:body-params valid-value + ::request/coercion :spec + :muuntaja/request {:format "application/json"}}) + valid-value)) + (is (= (c! {:body-params invalid-value + ::request/coercion :spec + :muuntaja/request {:format "application/json"}}) + valid-value))) + + (testing "no coercion" + (is (= (c! {:body-params valid-value + ::request/coercion nil + :muuntaja/request {:format "application/json"}}) + valid-value)) + (is (= (c! {:body-params invalid-value + ::request/coercion nil + :muuntaja/request {:format "application/json"}}) + invalid-value))))) (defn ok [body] {:status 200, :body body}) -(defn ok? [body] - (contains (ok body))) +(defn is-ok? [expected value] + (is (= (ok expected) (select-keys value [:status :body])))) (def responses {200 {:schema ::spec}}) @@ -107,51 +120,61 @@ (-> cs/default-options (assoc-in [:response :formats "application/json"] cs/json-transformer)))) -(fact "response-coercion" +(deftest response-coercion-test (let [c! coercion/coerce-response!] - (fact "default coercion" - (c! {::request/coercion :spec} - (ok valid-value) - responses) => (ok? valid-value) - (c! {::request/coercion :spec} - (ok invalid-value) - responses) => (throws) + (testing "default coercion" + (is-ok? valid-value + (c! {::request/coercion :spec} + (ok valid-value) + responses)) + (is (thrown? Exception + (c! {::request/coercion :spec} + (ok invalid-value) + responses))) (try (c! {::request/coercion :spec} (ok invalid-value) responses) (catch Exception e - (ex-data e) => (contains {:type :compojure.api.exception/response-validation - :coercion (coercion/resolve-coercion :spec) - :in [:response :body] - :spec st/spec? - :value invalid-value - :problems anything - :request {::request/coercion :spec}})))) - - (fact "format-based custom coercion" - (fact "request-negotiated response format" - (c! irrelevant - (ok invalid-value) - responses) => throws - (c! {:muuntaja/response {:format "application/json"} - ::request/coercion custom-coercion} - (ok invalid-value) - responses) => (ok? valid-value))) - - (fact "no coercion" - (c! {::request/coercion nil} - (ok valid-value) - responses) => (ok? valid-value) - (c! {::request/coercion nil} - (ok invalid-value) - responses) => (ok? invalid-value)))) + (is (= (-> (ex-data e) + (select-keys [:type :coercion :in :spec :value :problems :request]) + (update :spec (comp boolean st/spec?)) + (update :problems some?)) + {:type :compojure.api.exception/response-validation + :coercion (coercion/resolve-coercion :spec) + :in [:response :body] + :spec true + :value invalid-value + :problems true + :request {::request/coercion :spec}}))))) + + (testing "format-based custom coercion" + (testing "request-negotiated response format" + (is (thrown? Exception + (c! nil + (ok invalid-value) + responses))) + (is-ok? valid-value + (c! {:muuntaja/response {:format "application/json"} + ::request/coercion custom-coercion} + (ok invalid-value) + responses)))) + + (testing "no coercion" + (is-ok? valid-value + (c! {::request/coercion nil} + (ok valid-value) + responses)) + (is-ok? invalid-value + (c! {::request/coercion nil} + (ok invalid-value) + responses))))) (s/def ::x int?) (s/def ::y int?) (s/def ::xy (s/keys :req-un [::x ::y])) (s/def ::total pos-int?) -(facts "apis" +(deftest apis-test (let [app (api {:swagger {:spec "/swagger.json"} :coercion :spec} @@ -214,111 +237,120 @@ :handler (fn [{body-params :body-params}] (ok body-params))}})))] - (fact "query" + (testing "query" (let [[status body] (get* app "/query" {:x "1", :y 2})] - status => 200 - body => {:total 3}) + (is (= status 200)) + (is (= body {:total 3}))) (let [[status body] (get* app "/query" {:x "1", :y "kaks"})] - status => 400 - body => (contains - {:coercion "spec" - :in ["request" "query-params"] - :problems (one-of anything) - :spec string? - :type "compojure.api.exception/request-validation" - :value {:x "1", :y "kaks"}}))) - - (fact "body" + (is (= status 400)) + (is (= (-> body + (select-keys [:coercion :in :problems :spec :type :value]) + (update :problems count) + (update :spec string?)) + {:coercion "spec" + :in ["request" "query-params"] + :problems 1 + :spec true + :type "compojure.api.exception/request-validation" + :value {:x "1", :y "kaks"}})))) + + (testing "body" (let [[status body] (post* app "/body" (json-string {:x 1, :y 2, :z 3}))] - status => 200 - body => {:total 3})) + (is (= status 200)) + (is (= body {:total 3})))) - (fact "body-map" + (testing "body-map" (let [[status body] (post* app "/body-map" (json-string {:x 1, :y 2}))] - status => 200 - body => {:total 3}) + (is (= status 200)) + (is (= body {:total 3}))) (let [[status body] (post* app "/body-map" (json-string {:x 1}))] - status => 200 - body => {:total 1})) + (is (= status 200)) + (is (= body {:total 1})))) - (fact "body-string" + (testing "body-string" (let [[status body] (post* app "/body-string" (json-string "kikka"))] - status => 200 - body => {:body "kikka"})) + (is (= status 200)) + (is (= body {:body "kikka"})))) - (fact "query-params" + (testing "query-params" (let [[status body] (get* app "/query-params" {:x "1", :y 2})] - status => 200 - body => {:total 3}) + (is (= status 200)) + (is (= body {:total 3}))) (let [[status body] (get* app "/query-params" {:x "1", :y "a"})] - status => 400 - body => (contains {:coercion "spec" - :in ["request" "query-params"]}))) + (is (= status 400)) + (is (= (select-keys body [:coercion :in]) + {:coercion "spec" + :in ["request" "query-params"]})))) - (fact "body-params" + (testing "body-params" (let [[status body] (post* app "/body-params" (json-string {:x 1, :y 2}))] - status => 200 - body => {:total 3}) + (is (= status 200)) + (is (= body {:total 3}))) (let [[status body] (post* app "/body-params" (json-string {:x 1}))] - status => 200 - body => {:total 1}) + (is (= status 200)) + (is (= body {:total 1}))) (let [[status body] (post* app "/body-params" (json-string {:x "1"}))] - status => 400 - body => (contains {:coercion "spec" - :in ["request" "body-params"]}))) + (is (= status 400)) + (is (= (select-keys body [:coercion :in]) + {:coercion "spec" + :in ["request" "body-params"]})))) - (fact "response" + (testing "response" (let [[status body] (get* app "/response" {:x 1, :y 2})] - status => 200 - body => {:total 3}) + (is (= status 200)) + (is (= body {:total 3}))) (let [[status body] (get* app "/response" {:x -1, :y -2})] - status => 500 - body => (contains {:coercion "spec" - :in ["response" "body"]}))) + (is (= status 500)) + (is (= (select-keys body [:coercion :in]) + {:coercion "spec" + :in ["response" "body"]})))) - (fact "customer coercion & custom predicate" + (testing "customer coercion & custom predicate" (let [[status body] (get* app "/date/pass")] - status => 200) + (is (= status 200))) (let [[status body] (get* app "/date/fail")] - status => 500 - body => (contains {:coercion "custom" - :in ["response" "body"]}))) - (fact "resource" - (fact "parameters as specs" + (is (= status 500)) + (is (= (select-keys body [:coercion :in]) + {:coercion "custom" + :in ["response" "body"]})))) + (testing "resource" + (testing "parameters as specs" (let [[status body] (get* app "/resource" {:x 1, :y 2})] - status => 200 - body => {:total 3}) + (is (= status 200)) + (is (= body {:total 3}))) (let [[status body] (get* app "/resource" {:x -1, :y -2})] - status => 500 - body => (contains {:coercion "spec" - :in ["response" "body"]}))) + (is (= status 500)) + (is (= (select-keys body [:coercion :in]) + {:coercion "spec" + :in ["response" "body"]})))) - (fact "parameters as data-specs" + (testing "parameters as data-specs" (let [[status body] (post* app "/resource" (json-string {:x 1, :y 2}))] - status => 200 - body => {:total 3}) + (is (= status 200)) + (is (= body {:total 3}))) (let [[status body] (post* app "/resource" (json-string {:x 1}))] - status => 200 - body => {:total 1}) + (is (= status 200)) + (is (= body {:total 1}))) (let [[status body] (post* app "/resource" (json-string {:x -1, :y -2}))] - status => 500 - body => (contains {:coercion "spec" - :in ["response" "body"]})))) + (is (= status 500)) + (is (= (select-keys body [:coercion :in]) + {:coercion "spec" + :in ["response" "body"]}))))) - (fact "extra keys are stripped from body-params before validation" - (fact "for resources" + (testing "extra keys are stripped from body-params before validation" + (testing "for resources" (let [[status body] (put* app "/resource" (json-string {:x 1, :y 2 ::kikka "kakka"}))] - status => 200 - body => {:x 1, :y 2})) - (fact "for endpoints" + (is (= status 200)) + (is (= body {:x 1, :y 2})))) + (testing "for endpoints" (let [[status body] (put* app "/body" (json-string {:x 1, :y 2 ::kikka "kakka"}))] - status => 200 - body => {:x 1, :y 2}))) + (is (= status 200)) + (is (= body {:x 1, :y 2}))))) - (fact "generates valid swagger spec" - (validator/validate app) =not=> (throws)) + (testing "generates valid swagger spec" + (is (do (validator/validate app) true))) - (fact "swagger spec has all things" + (testing "swagger spec has all things" (let [total-schema {:description "", :schema {:properties {:total {:format "int64", @@ -326,179 +358,178 @@ :type "integer"}}, :required ["total"], :type "object"}}] - (get-spec app) - => (contains - {:basePath "/" - :consumes ["application/json" - "application/transit+msgpack" - "application/transit+json" - "application/edn"] - :definitions {} - :info {:title "Swagger API" :version "0.0.1"} - :paths {"/body" {:post {:parameters [{:description "" - :in "body" - :name "compojure.api.coercion.spec-coercion-test/xy" - :required true - :schema {:properties {:x {:format "int64" - :type "integer"} - :y {:format "int64" - :type "integer"}} - :required ["x" "y"] - :title "compojure.api.coercion.spec-coercion-test/xy" - :type "object"}}] - :responses {:default {:description ""}}} - :put {:parameters [{:description "" - :in "body" - :name "compojure.api.coercion.spec-coercion-test/xy" - :required true - :schema {:properties {:x {:format "int64" - :type "integer"} - :y {:format "int64" - :type "integer"}} - :required ["x" "y"] - :title "compojure.api.coercion.spec-coercion-test/xy" - :type "object"}}] - :responses {:default {:description ""}}}} - "/body-map" {:post {:parameters [{:description "" - :in "body" - :name "" - :required true - :schema {:properties {:x {:format "int64" - :type "integer"} - :y {:format "int64" - :type "integer"}} - :required ["x"] - :type "object"}}] - :responses {:default {:description ""}}}} - "/body-params" {:post {:parameters [{:description "" - :in "body" - :name "" - :required true - :schema {:properties {:x {:format "int64" - :type "integer"} - :y {:format "int64" - :type "integer"}} - :required ["x"] - :type "object"}}] - :responses {:default {:description ""}}}} - "/body-string" {:post {:parameters [{:description "" - :in "body" - :name "" - :required true - :schema {:type "string"}}] - :responses {:default {:description ""}}}} - "/date/fail" {:get {:responses {:200 {:description "" - :schema {:properties {:date {:default "2017-10-12T05:04:57.585Z"}} - :required ["date"] - :type "object"}} - :default {:description ""}}}} - "/date/pass" {:get {:responses {:200 {:description "" - :schema {:properties {:date {:default "2017-10-12T05:04:57.585Z"}} - :required ["date"] - :type "object"}} - :default {:description ""}}}} - "/query" {:get {:parameters [{:description "" - :format "int64" - :in "query" - :name "x" - :required true - :type "integer"} - {:description "" - :format "int64" - :in "query" - :name "y" - :required true - :type "integer"}] - :responses {:default {:description ""}}}} - "/query-params" {:get {:parameters [{:description "" - :format "int64" - :in "query" - :name "x" - :required true - :type "integer"} - {:description "" - :format "int64" - :in "query" - :name "y" - :required true - :type "integer"}] - :responses {:default {:description ""}}}} - "/resource" {:get {:parameters [{:description "" - :format "int64" - :in "query" - :name "x" - :required true - :type "integer"} - {:description "" - :format "int64" - :in "query" - :name "y" - :required true - :type "integer"}] - :responses {:200 {:description "" - :schema {:properties {:total {:format "int64" - :minimum 1 - :type "integer"}} - :required ["total"] - :type "object"}} - :default {:description ""}}} - :post {:parameters [{:description "" - :in "body" - :name "" - :required true - :schema {:properties {:x {:format "int64" - :type "integer"} - :y {:format "int64" - :type "integer"}} - :required ["x"] - :type "object"}}] - :responses {:200 {:description "" - :schema {:properties {:total {:format "int64" - :minimum 1 - :type "integer"}} - :required ["total"] - :type "object"}} - :default {:description ""}}} - :put {:parameters [{:description "" - :in "body" - :name "compojure.api.coercion.spec-coercion-test/xy" - :required true - :schema {:properties {:x {:format "int64" - :type "integer"} - :y {:format "int64" - :type "integer"}} - :required ["x" "y"] - :title "compojure.api.coercion.spec-coercion-test/xy" - :type "object"}}] - :responses {:default {:description ""}}}} - "/response" {:get {:parameters [{:description "" - :format "int64" - :in "query" - :name "x" - :required true - :type "integer"} - {:description "" - :format "int64" - :in "query" - :name "y" - :required true - :type "integer"}] - :responses {:200 {:description "" - :schema {:properties {:total {:format "int64" - :minimum 1 - :type "integer"}} - :required ["total"] - :type "object"}} - :default {:description ""}}}}} - :produces ["application/json" - "application/transit+msgpack" - "application/transit+json" - "application/edn"] - :swagger "2.0"}))))) + (is (= (get-spec app) + {:basePath "/" + :consumes ["application/json" + "application/transit+msgpack" + "application/transit+json" + "application/edn"] + :definitions {} + :info {:title "Swagger API" :version "0.0.1"} + :paths {"/body" {:post {:parameters [{:description "" + :in "body" + :name "compojure.api.coercion.spec-coercion-test/xy" + :required true + :schema {:properties {:x {:format "int64" + :type "integer"} + :y {:format "int64" + :type "integer"}} + :required ["x" "y"] + :title "compojure.api.coercion.spec-coercion-test/xy" + :type "object"}}] + :responses {:default {:description ""}}} + :put {:parameters [{:description "" + :in "body" + :name "compojure.api.coercion.spec-coercion-test/xy" + :required true + :schema {:properties {:x {:format "int64" + :type "integer"} + :y {:format "int64" + :type "integer"}} + :required ["x" "y"] + :title "compojure.api.coercion.spec-coercion-test/xy" + :type "object"}}] + :responses {:default {:description ""}}}} + "/body-map" {:post {:parameters [{:description "" + :in "body" + :name "" + :required true + :schema {:properties {:x {:format "int64" + :type "integer"} + :y {:format "int64" + :type "integer"}} + :required ["x"] + :type "object"}}] + :responses {:default {:description ""}}}} + "/body-params" {:post {:parameters [{:description "" + :in "body" + :name "" + :required true + :schema {:properties {:x {:format "int64" + :type "integer"} + :y {:format "int64" + :type "integer"}} + :required ["x"] + :type "object"}}] + :responses {:default {:description ""}}}} + "/body-string" {:post {:parameters [{:description "" + :in "body" + :name "" + :required true + :schema {:type "string"}}] + :responses {:default {:description ""}}}} + "/date/fail" {:get {:responses {:200 {:description "" + :schema {:properties {:date {:default "2017-10-12T05:04:57.585Z"}} + :required ["date"] + :type "object"}} + :default {:description ""}}}} + "/date/pass" {:get {:responses {:200 {:description "" + :schema {:properties {:date {:default "2017-10-12T05:04:57.585Z"}} + :required ["date"] + :type "object"}} + :default {:description ""}}}} + "/query" {:get {:parameters [{:description "" + :format "int64" + :in "query" + :name "x" + :required true + :type "integer"} + {:description "" + :format "int64" + :in "query" + :name "y" + :required true + :type "integer"}] + :responses {:default {:description ""}}}} + "/query-params" {:get {:parameters [{:description "" + :format "int64" + :in "query" + :name "x" + :required true + :type "integer"} + {:description "" + :format "int64" + :in "query" + :name "y" + :required true + :type "integer"}] + :responses {:default {:description ""}}}} + "/resource" {:get {:parameters [{:description "" + :format "int64" + :in "query" + :name "x" + :required true + :type "integer"} + {:description "" + :format "int64" + :in "query" + :name "y" + :required true + :type "integer"}] + :responses {:200 {:description "" + :schema {:properties {:total {:format "int64" + :minimum 1 + :type "integer"}} + :required ["total"] + :type "object"}} + :default {:description ""}}} + :post {:parameters [{:description "" + :in "body" + :name "" + :required true + :schema {:properties {:x {:format "int64" + :type "integer"} + :y {:format "int64" + :type "integer"}} + :required ["x"] + :type "object"}}] + :responses {:200 {:description "" + :schema {:properties {:total {:format "int64" + :minimum 1 + :type "integer"}} + :required ["total"] + :type "object"}} + :default {:description ""}}} + :put {:parameters [{:description "" + :in "body" + :name "compojure.api.coercion.spec-coercion-test/xy" + :required true + :schema {:properties {:x {:format "int64" + :type "integer"} + :y {:format "int64" + :type "integer"}} + :required ["x" "y"] + :title "compojure.api.coercion.spec-coercion-test/xy" + :type "object"}}] + :responses {:default {:description ""}}}} +"/response" {:get {:parameters [{:description "" + :format "int64" + :in "query" + :name "x" + :required true + :type "integer"} + {:description "" + :format "int64" + :in "query" + :name "y" + :required true + :type "integer"}] + :responses {:200 {:description "" + :schema {:properties {:total {:format "int64" + :minimum 1 + :type "integer"}} + :required ["total"] + :type "object"}} + :default {:description ""}}}}} + :produces ["application/json" + "application/transit+msgpack" + "application/transit+json" + "application/edn"] + :swagger "2.0"})))))) (s/def ::id pos-int?) -(fact "spec coercion in context" +(deftest spec-coercion-in-context-test (let [app (context "/product/:id" [] :coercion :spec :path-params [id :- ::id] @@ -506,13 +537,12 @@ :return ::id (ok id))) [status body] (get* app "/product/1/foo")] - status => 200 - body => 1)) + (is (= status 200)) + (is (= body 1)))) -(comment - (require '[expound.alpha]) +(deftest expound-test - (facts "custom spec printer" + (testing "custom spec printer" (let [printer (expound.alpha/custom-printer {:theme :figwheel-theme, :print-specs? false}) app (api {:coercion :spec @@ -529,15 +559,15 @@ :body-params [x :- int?, y :- int?] :return {:total pos-int?} (ok {:total (+ x y)})))] - (fact "success" + (testing "success" (let [[status body] (post* app "/math" (json-string {:x 1, :y 2}))] - status => 200 - body => {:total 3})) + (is (= status 200)) + (is (= body {:total 3})))) - (fact "request failure" + (testing "request failure" (let [[status] (post* app "/math" (json-string {:x 1, :y "2"}))] - status => 400)) + (is (= status 400)))) - (fact "response failure" + (testing "response failure" (let [[status] (post* app "/math" (json-string {:x 1, :y -2}))] - status => 500))))) + (is (= status 500)))))))