Compojure-api not respecting body from ring.mock.requests - clojure

I'm trying to use the ring.mock.requests library to test an http service. I have this bit of code
(auth-routes
(->
(mock/request :post "/auth/user")
(mock/body {:username "user" :password "pass"})
(mock/content-type "application/json")))
The inner part with the thread-first macro seems to work correctly, but it fails when I try to make the mock request to auth-routes
And this is my route:
(def auth-routes
(context "/auth" []
:tags ["Authentication"]
(POST "/user" []
:return s/Uuid
:body [request m/User]
:summary "Create new user"
(ok (auth/create-user request)))) ...)
This is my schema:
(def User {:username s/Str
:password s/Str})
The exception I'm seeing is
clojure.lang.ExceptionInfo
Request validation failed
{:error (not (map? nil)), :type :compojure.api.exception/request-validation}
It looks like my route is getting nil as the body, and I expect {:username "user" :password "pass"} to be there.
What am I doing wrong and how do I pass a body to this route?

I think you should serialize your data as a json string in your test. Using cheshire:
(cheshire.core/generate-string {:username "user" :password "pass"})
Take a look here or here.

Related

Routing image certain types in pedestal.io

I am creating routes for:
/
/*.jpg
/*.png
in pedestal.io.
My bad code, that returns boot.user=> clojure.lang.ExceptionInfo: route conflict, is this
(def routes
(route/expand-routes
#{
["/" :get [coerce-body content-neg-intc respond-main]
:route-name :main]
["/*.jpg" :get [(ringmid/resource "/")]
:route-name :jpg]
["/*.png" :get [(ringmid/resource "/")]
:route-name :png]
}))
Any idea how to improve it?

Get Route record (or :name) for a given request

I'm using compojure-api, and I'm looking for a function that, given my api route structure thingie and a request, returns the Route record (or :name) for that request, without applying its handler.
I've been able to find kind of the inverse of what I'm looking for in compojure.api.routes/path-for, which, given a :name, returns the path for the corresponding route. In the same namespace there are also functions like get-routes which seem promising, but I've yet to find exactly what I'm looking for.
In other words, given this simple example
(defapi my-api
(context "/" []
(GET "/myroute" request
:name :my-get
(ok))
(POST "/myroute" request
:name :my-post
(ok))))
I'm looking for a function foo that works like this
(foo my-api (mock/request :get "/myroute"))
;; => #Route{:path "/myroute", :method :get, :info {:name :my-get, :public {:x-name :my-get}}}
;; or
;; => :my-get
Any ideas?
my-api is defined as a Route Record, so you can evaluate it at the repl to see what it looks like:
#Route{:info {:coercion :schema},
:childs [#Route{:childs [#Route{:path "/",
:info {:static-context? true},
:childs [#Route{:path "/myroute",
:method :get,
:info {:name :my-get, :public {:x-name :my-get}}}
#Route{:path "/myroute",
:method :post,
:info {:name :my-post, :public {:x-name :my-post}}}]}]}]}
There are helpers in compojure.api.routes to transform the structure:
(require '[compojure.api.routes :as routes])
(routes/get-routes my-api)
; [["/myroute" :get {:coercion :schema, :static-context? true, :name :my-get, :public {:x-name :my-get}}]
; ["/myroute" :post {:coercion :schema, :static-context? true, :name :my-post, :public {:x-name :my-post}}]]
, which effectively flattens the route tree while retaining the order. For reverse routing there is:
(-> my-api
routes/get-routes
routes/route-lookup-table)
; {:my-get {"/myroute" {:method :get}}
; :my-post {"/myroute" {:method :post}}}
More utilities can be added if needed.
Hope this helps.

Request map passed to my anonymous function inside a Compojure handler

I'm two days into learning Clojure by writing a simple REST server using ring-clojure and Compojure with ring-json's wrap-json-body middleware.
So far, I have:
A vector users containing the users (with a couple of default users):
(def users [{:id 0 :username "aname"}
{:id 1 :username "anothername"}])
A function (form?) save-user that accepts a map (user) and looks for existing users with the same username. If the username is available, I overwrite the users vector to include the new user before returning HTTP 201. If the username is taken, I simply return HTTP 400:
(defn save-user [user]
(prn users)
(if
(not-any? #(= (:username %) (:username user)) users)
(fn [request]
(def users (conj users user))
(status
(response (str "Saved user with username: " (:username user)))
201))
(status
(response (str "User with username '" (:username user) "' already exists"))
400)))
A route for POST /users which calls save-user with the received map:
(defroutes app-routes
(POST "/users" request (save-user (:body request))))
I don't think it matters, but the middleware is applied like this:
(def app
(-> app-routes
(wrap-cors :access-control-allow-origin "*" :access-control-allow-methods "*")
(wrap-json-response)
(wrap-keyword-params)
(wrap-json-body {:keywords? true :bigdecimals? true})
(wrap-defaults (assoc site-defaults :security false))))
My problem:
For whatever reason, the entire request map is passed to the function i pass as then inside the if. Printing it:
(if
(not-any? #(= (:username %) (:username user)) users)
(fn [request]
(prn request))
...
... gives me this:
{:ssl-client-cert nil, :cookies {}, :remote-addr "0:0:0:0:0:0:0:1", :params {}, :flash nil, :route-params {}, :headers {"host" "localhost:3000", "accept" "*/*", "content-length" "42", "content-type" "application/json", "user-agent" "curl/7.43.0"}, :server-port 3000, :content-length 42, :form-params {}, :compojure/route [:post "/users"], :session/key nil, :query-params {}, :content-type "application/json", :character-encoding nil, :uri "/users", :server-name "localhost", :query-string nil, :body {:username "testusername", :password "testpassword"}, :multipart-params {}, :scheme :http, :request-method :post, :session {}}
The same happens if I pass an anonymous function as the if's else. However, nothing wrong happens when I only pass (status ...), like in the code above.
From what I understand, the request map shouldn't be available inside save-user at all, since it's not passed as an argument. Why is it passed to my anonymous function, and is there any way to simply ignore it?
In save-user you are returning a function that takes a request and returns a response (a handler).
I suppose you should instead just return the response directly. Just replace (fn [request] with (do to wrap multiple expressions in a single form.
(def users (atom [{:id 0 :username "aname"}
{:id 1 :username "anothername"}]))
(defn save-user [user]
(if (not-any? #(= (:username %) (:username user)) #users)
(do
(swap! users conj user)
(status
(response (str "Saved user with username: " (:username user)))
201))
(status
(response (str "User with username '" (:username user) "' already exists"))
400)))
I’ve also changed the global users var to be an atom. Redefining global vars from inside a function is a big no-no in Clojure.

how to load another page in cljs after post request using cljs-ajax

I have created a login page login.cljs and handler.clj.here is my code
login.cljs:-
(defn handler [response]
(let[status (aget (clj->js response) "status")
msg (str (aget (clj->js response) "greeting"))]
(if (= status 200)
(home/home-root)
(js/alert "Invalid username or password"))
))
(defn handle-submit [e app owner]
(let [[user user-node] (value-from-node owner "username")
[pass pass-node] (value-from-node owner "password")]
(POST "/hello"
{:params {:username user :password pass}
:handler handler
:error-handler error-handler
:format :json
:response-format :json})
))
(defn login-view [app owner]
(reify
om/IRender(render [this]
(dom/div nil
(dom/h1 nil "Login")
(dom/input #js {:type "text" :placeholder "Your Name" :ref "username"})
(dom/input #js {:type "password" :placeholder "Your Password" :ref "password"})
(dom/button
#js { :onClick (fn [event] (handle-submit event app owner))}
"submit")
))))
(defn say-hello []
(om/root
{} login-view (.getElementById js/document "content")))
home.cljs:-
(defn home-view [app owner]
(reify
om/IRender
(render [this]
(dom/div nil "Welcome"
))))
(defn home-root []
(om/root
{}
home-view
(.getElementById js/document "content"))
handler.clj:-
(defn login-check! [request]
(let [user (get-in request [:params :username])
pass (get-in request [:params :password])]
(cond
(and(= user "admin")(= pass "admin")) (response/response {:greeting user
:status 200})
:else (response/response {:greeting "Invalid username or password"
:status 403}))
))
(defroutes app-routes
(GET "/" [] (response/resource-response "index.html" {:root "public"}))
(route/resources "/")
(POST "/hello" request (login-check! request))
(GET "/home" request (response/response {:body "home"}))
(route/not-found "Not Found"))
I need to load home page view when its a valid login.
here i have called another cljs method 'home/home-root' but its not working.
Here i got one solution..I don't know how much its correct but its working good
I have created a new defn page using reagent as follows:-
(defn page [page-component]
(reagent/render-component [page-component]
(.getElementById js/document "content")))
and called this function from login.cljs handler method:
(defn handler [response]
(page home-root))
now the page method will be called and home-root cljs will be passed to it.

How does one configure sibling Pedestal routes so that all are accessible when using :constraints?

I am creating a toy Pedestal service intended to have the following resources:
/
/movies
/movies/today
/movies/:iso-date where :iso-date matches ####-##-##
The constraint for the last route is defined with the following snippet:
^:constraints {:iso-date #"\d{4}-\d{2}-\d{2}"}
Whenever the route containing this constraint is present in the route table I am unable to GET its sibling route /movies/today; instead I am getting a "Not Found" response instead. When the constraint-having route is removed, however, a GET of /movies/today succeeds.
The Pedestal routes I have defined using terse format look like so:
(defroutes routes
[[["/" {:get root-page}
["/movies" ^:interceptors [fetch-movies]
{:get movies-page}
["/today" {:get movies-for-today-page}]
["/:iso-date" ^:constraints {:iso-date #"\d{4}-\d{2}-\d{2}"}
{:get movies-for-date-page}]]]]])
Have I constructed this route table correctly in order to achieve the routing behaviour that I want?
NB: Printing the compiled routes gives me the result that I would expect in that all of the routes are present the generated :path-re regexes match as expected at the REPL:
({:path-parts [""],
:path-params [],
:interceptors
[{:name :foobar.service/root-page,
:enter
#object[io.pedestal.interceptor.helpers$before$fn__7359 0x14501070 "io.pedestal.interceptor.helpers$before$fn__7359#14501070"],
:leave nil,
:error nil}],
:path "/",
:method :get,
:path-re #"/\Q\E",
:route-name :foobar.service/root-page}
{:path-parts ["" "movies"],
:path-params [],
:interceptors
[{:name :foobar.service/fetch-movies,
:enter
#object[io.pedestal.interceptor.helpers$on_request$fn__7401 0x2aa85cc4 "io.pedestal.interceptor.helpers$on_request$fn__7401#2aa85cc4"],
:leave nil,
:error nil}
{:name :foobar.service/movies-page,
:enter
#object[io.pedestal.interceptor.helpers$before$fn__7359 0x30ffc3c0 "io.pedestal.interceptor.helpers$before$fn__7359#30ffc3c0"],
:leave nil,
:error nil}],
:path "/movies",
:method :get,
:path-re #"/\Qmovies\E",
:route-name :foobar.service/movies-page}
{:path-parts ["" "movies" "today"],
:path-params [],
:interceptors
[{:name :foobar.service/fetch-movies,
:enter
#object[io.pedestal.interceptor.helpers$on_request$fn__7401 0x2aa85cc4 "io.pedestal.interceptor.helpers$on_request$fn__7401#2aa85cc4"],
:leave nil,
:error nil}
{:name :foobar.service/movies-for-today-page,
:enter
#object[io.pedestal.interceptor.helpers$before$fn__7359 0x3726fc3b "io.pedestal.interceptor.helpers$before$fn__7359#3726fc3b"],
:leave nil,
:error nil}],
:path "/movies/today",
:method :get,
:path-re #"/\Qmovies\E/\Qtoday\E",
:route-name :foobar.service/movies-for-today-page}
{:path-parts ["" "movies" :iso-date],
:path-params [:iso-date],
:interceptors
[{:name :foobar.service/fetch-movies,
:enter
#object[io.pedestal.interceptor.helpers$on_request$fn__7401 0x2aa85cc4 "io.pedestal.interceptor.helpers$on_request$fn__7401#2aa85cc4"],
:leave nil,
:error nil}
{:name :foobar.service/movies-for-date-page,
:enter
#object[io.pedestal.interceptor.helpers$before$fn__7359 0x93fb20b "io.pedestal.interceptor.helpers$before$fn__7359#93fb20b"],
:leave nil,
:error nil}],
:path "/movies/:iso-date",
:path-constraints {:iso-date "(\\d{4}-\\d{2}-\\d{2})"},
:query-constraints {},
:method :get,
:path-re #"/\Qmovies\E/(\d{4}-\d{2}-\d{2})",
:route-name :foobar.service/movies-for-date-page})
I solved this problem in pedestal version 0.4.1-SNAPSHOT.
(io.pedestal.http.route/router my-routes :linear-search)
Use :linear-search, instead of :prefix-tree.