I want to write a test for a simple POST request using ring.mock
- something like this:
(testing "id post route"
(let [response (app (mock/request :post "/" {:id "Foo"}))]
(is (= 302 (:status response)))))
However, since I use the wrap-csrf
middleware I get a 403 status response since I don't provide an anti-forgery token.
Is there a way to write POST tests with ring.mock
without disabling the wrap-csrf
middleware?
First, I'd like to echo some of the points about the approach and reasoning by another post here:
app
which represents the entire application, but you can circumvent the CSRF in order to test endpoints directly if desirable. This would allow you to test the given endpoint, but wouldn't test that the CSRF protection is working correctly.Assuming the above, you might need to test that the login procedure is working correctly and that CSRF is integrated. The following test helpers should help you along this path:
(ns myapp.test.handler-test
(:require [clojure.test :refer :all]
[ring.mock.request :refer [request]]
[net.cgrand.enlive-html :as html]
[myapp.handler :refer [app]]))
(defn get-session
"Given a response, grab out just the key=value of the ring session"
[resp]
(let [headers (:headers resp)
cookies (get headers "Set-Cookie")
session-cookies (first (filter #(.startsWith % "ring-session") cookies))
session-pair (first (clojure.string/split session-cookies #";"))]
session-pair))
(defn get-csrf-field
"Given an HTML response, parse the body for an anti-forgery input field"
[resp]
(-> (html/select (html/html-snippet (:body resp)) [:input#__anti-forgery-token])
first
(get-in [:attrs :value])))
(defn get-login-session!
"Fetch a login page and return the associated session and csrf token"
[]
(let [resp (app (request :get "/login"))]
{:session (get-session resp)
:csrf (get-csrf-field resp)}))
(defn login!
"Login a user given a username and password"
[username password]
(let [{:keys [csrf session]} (get-login-session!)
req (-> (request :post "/login")
(assoc :headers {"cookie" session})
(assoc :params {:username username
:password password})
(assoc :form-params {"__anti-forgery-token" csrf}))]
(app req)
session))
In the above, I'm assuming that the /login
page uses the __anti-forgery-token
hidden input and that you want to test against this. You might also consider placing the CSRF token in the session data, which makes it easier to test with tools like curl
which can save session data from one response to a file for use in successive requests.
Since I'm pulling the token out of the HTML body, I decided to use enlive
so that I could use a CSS selector to define where I'm pulling this data from in a straightforward and declarative way.
To make use of the above, you can call login!
and then use the session data it returns in successive requests that you want to be authenticated with that same session, such as:
(deftest test-home-page-authentication
(testing "authenticated response"
(let [session (login! "bob" "bob")
request (-> (request :get "/")
(assoc :headers {"cookie" session}))]
(is (= 200 (:status (app request)))))))
This assumes you have a username/password of bob and bob set up in whatever auth backend you are using. You'll likely need to have a username/password setup step that creates this user before this login procedure will work.
What do you want to test? It's not clear from your question and it's not clear why you shouldn't disable anti-forgery middleware at all.
If you are testing a web service you shouldn't use CSRF tokens at all and switch to a different security mechanism (e.g. authorization headers, API tokens etc.)
If you want to test end-to-end flow including CSRF logic, then you need to obtain a valid CSRF token by calling appropriate URL first and extracting it from the response (e.g. parsing the hidden field) along with the session ID so you can use them in the test request.
If you want to test your handler
logic then test it without the wrapping "infrastructure" middleware. There is no point in mocking anti-forgery middleware if you can just not apply it to your handler function in your tests and the problem disappears.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With