Testing 具有防伪和环形模拟的测试柱路线

Testing 具有防伪和环形模拟的测试柱路线,testing,clojure,mocking,csrf,ring,Testing,Clojure,Mocking,Csrf,Ring,我想使用ring.mock,为一个简单的POST请求编写一个测试,如下所示: (testing "id post route" (let [response (app (mock/request :post "/" {:id "Foo"}))] (is (= 302 (:status response))))) 然而,由于我使用wrap csrf中间件,我得到了403状态响应,因为我没有提供防伪令牌 有没有一种方法可以在不禁用wrap csrf中间件的情况下使用ring.m

我想使用
ring.mock
,为一个简单的POST请求编写一个测试,如下所示:

(testing "id post route"
    (let [response (app (mock/request :post "/" {:id "Foo"}))]
      (is (= 302 (:status response)))))
然而,由于我使用
wrap csrf
中间件,我得到了403状态响应,因为我没有提供防伪令牌


有没有一种方法可以在不禁用
wrap csrf
中间件的情况下使用
ring.mock
编写POST测试?

您想测试什么?您的问题并不清楚,也不清楚为什么不应该禁用防伪中间件

  • 如果您正在测试一个web服务,那么您根本不应该使用CSRF令牌,而应该切换到不同的安全机制(例如授权头、API令牌等)

  • 如果要测试包括CSRF逻辑的端到端流,则需要首先调用适当的URL并从响应中提取(例如,解析隐藏字段)以及会话ID,从而获得有效的CSRF令牌,以便在测试请求中使用它们

  • 如果要测试
    处理程序
    逻辑,请在不使用包装式“基础设施”中间件的情况下进行测试。如果您不能在测试中将防伪中间件应用于处理程序函数,并且问题消失,那么模拟防伪中间件就没有意义了


  • 首先,我想在此回应另一篇文章中关于方法和推理的一些观点:

  • CSRF令牌仅对HTML登录是必需的,但不需要或不需要 适合于web服务
  • 使用Ring/Compojure,可以直接测试处理程序,或者只将它们封装在必要的中间件中。在下面的示例中,我需要表示整个应用程序的
    app
    ,但是如果需要,您可以绕过CSRF直接测试端点。这将允许您测试给定端点,但无法测试CSRF保护是否正常工作
  • 假设存在上述情况,您可能需要测试登录过程是否正常工作,以及是否集成了CSRF。以下测试帮助程序应该可以帮助您完成此任务:

    (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))
    
    在上面,我假设
    /login
    页面使用
    \uuu-anti-forgery-token
    隐藏输入,并且您希望对此进行测试。您还可以考虑将CSRF令牌放置在会话数据中,这样就更容易用诸如<代码> CURL< /COD>之类的工具进行测试,这样可以将会话数据从一个响应保存到一个文件中,以便在后续请求中使用。 因为我要从HTML主体中提取标记,所以我决定使用
    enlive
    ,这样我就可以使用CSS选择器以一种简单且声明性的方式定义从何处提取这些数据

    要使用上述功能,您可以调用
    login
    然后使用它在您希望通过同一会话进行身份验证的连续请求中返回的会话数据,例如:

     (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)))))))
    

    这假设您在使用的任何身份验证后端中都设置了用户名/密码bob和bob。您可能需要有一个用户名/密码设置步骤来创建此用户,然后此登录过程才能工作。

    我们可以使用
    在测试中禁用
    csrf
    (assoc site defaults:security false)
    。完整的代码如下所示:

    (testing "id post route"
        (let [response (app (mock/request :post "/" {:id "Foo"}))]
          (is (= 302 (:status response)))))
    
    ;在utilities.testing中创建测试应用程序的副本
    ; 通过使用测试中间件包装处理程序
    (ns.测试
    (:require[your-web-app.handler:参考[path handler]]
    [ring.middleware.defaults:参考[wrap defaults site defaults]])
    ; 禁用CSRF进行测试
    (def应用程序
    (->路径处理程序
    (包装默认值(关联站点默认值:安全性假)))
    
    现在您可以在测试中使用此应用程序

    (ns users.views-test)
    (:需要[utilities.testing:参考[app]]
    ;...
    ))
    ;...
    (测试“身份证张贴路线”
    (let[response(app(mock/request:post)/“{:id“Foo”}”)]
    (是(=302(:状态响应(()))))