使用Midje伪造朋友凭证功能

时间:2014-11-14 08:08:58

标签: clojure ring midje

我正在尝试使用Midje单独测试我的路由。对于某些访问数据库的路由,我可以使用(provided ...)将路由与真正的数据库调用隔离开来。我已经介绍了Friend进行身份验证,但我无法伪造对凭证功能的调用。

我的凭证功能看起来像这样(它是这样实现的,因为我不想让它被调用):

(defn cred-fn
  [creds]
  (println (str "hey look I got called with " creds))
  (throw (Exception.)))

路由的中间件看起来像这样:

(def app 
  (-> app-routes
      (wrap-json-body {:keywords? true :bigdecimals? true})
      wrap-json-response
      (wrap-defaults defaults)
      (friend/authenticate
       {:unauthorized-handler json-auth/login-failed
        :workflows [(json-auth/json-login
                     :login-uri "/login"
                     :login-failure-handler json-auth/login-failed
                     :credential-fn auth/cred-fn)]})
      (ring-session/wrap-session)))

我也尝试过不使用auth-json-workflow,路由的实现看起来几乎相同,我可以补充说,如果它有帮助,但我得到相同的结果。

然后我的测试看起来像这样(使用ring-mock):

(defn post [url body]
  (-> (mock/request :post url body)
      (mock/content-type "application/json")
      app))

(fact "login with incorrect username and password returns unauthenticated"
  (:status (post "/login" invalid-auth-account-json)) => 401
  (provided
    (auth/cred-fn anything) => nil))
(fact "login with correct username and password returns success"
  (:status (post "/login" auth-account-json)) => 200
  (provided
    (auth/cred-fn anything) => {:identity "root"}))

然后我得到以下输出运行测试:

hey look I got called with {:password "admin_password", :username "not-a-user"}
FAIL at (handler.clj:66)
These calls were not made the right number of times:
    (auth/cred-fn anything) [expected at least once, actually never called]
FAIL "routes - authenticated routes - login with incorrect username and password returns unauthenticated" at (handler.clj:64)
    Expected: 401
      Actual: java.lang.Exception
          clojure_api_seed.authentication$cred_fn.invoke(authentication.clj:23)

hey look I got called with {:password "admin_password", :username "root"}
FAIL at (handler.clj:70)
These calls were not made the right number of times:
    (auth/cred-fn anything) [expected at least once, actually never called]

FAIL "routes - authenticated routes - login with correct username and password returns success" at (handler.clj:68)
    Expected: 200
      Actual: java.lang.Exception
          clojure_api_seed.authentication$cred_fn.invoke(authentication.clj:23)

所以我可以看到所提供的声明没有生效,我不知道为什么。有什么想法吗?

2 个答案:

答案 0 :(得分:1)

我最近遇到了类似的问题,经过一番挖掘我 我想我明白为什么会这样。我们来看看如何 auth/cred-fn的绑定随时间而变化。

(clojure.pprint/pprint (macroexpand '(defn cred-fn
                                                [creds]
                                                (println (str "hey look I got called with " creds))
                                                (throw (Exception.)))))
(def
 cred-fn
 (clojure.core/fn
  ([creds]
   (println (str "hey look I got called with " creds))
   (throw (Exception.)))))

如上所示,defn宏将当前命名空间中的符号cred-fn实例化,并将其绑定到Var引用 你的虚拟功能。

(def app 
    (-> app-routes
        (wrap-json-body {:keywords? true :bigdecimals? true})
        wrap-json-response
        (wrap-defaults defaults)
        (friend/authenticate
        {:unauthorized-handler json-auth/login-failed
        :workflows [(json-auth/json-login
                    :login-uri "/login"
                    :login-failure-handler json-auth/login-failed
                    :credential-fn auth/cred-fn)]})
        (ring-session/wrap-session)))

这是重要的一块。在编译时,我们通过线程 app-routes通过一系列功能。其中一个功能 是friend/authenticate,它使用密钥:workflows获取地图。 :workflows的值是填充了结果的向量 致json-auth/json-login的电话,该电话会收到auth/credential-fn 作为参数。记住,我们在def里面,所以这就是全部 发生在编译时。我们在中查找符号cred-fn auth名称空间,并传入符号绑定的Var 至。在这一点上,这仍然是虚拟实现。 据推测,json-auth/json-login捕获了这种实现 并设置一个调用它的请求处理程序。

(fact "login with incorrect username and password returns unauthenticated"
    (:status (post "/login" invalid-auth-account-json)) => 401
    (provided
    (auth/cred-fn anything) => nil))

现在我们在运行时。在我们的先决条件下,Midje重新启动了 符号auth/cred-fn到引用模拟的Var。但是 我们auth/cred-fn'时已经捕获def的值 app在编译时。

那么你发布的变通方法怎么样? (这实际上是导致我的线索 尤里卡时刻 - 谢谢你。)

(defn another-fn []
  (println (str "hey look I got called"))
  (throw (Exception.)))

(defn cred-fn [creds]
  (another-fn))

在你的测试中......

(fact "login with incorrect username and password returns unauthenticated"
  (:status (post "/login" invalid-auth-account-json)) => 401
  (provided
    (auth/another-fn) => nil))

(fact "login with correct username and password returns success"
  (:status (post "/login" auth-account-json)) => 200
  (provided
    (auth/another-fn) => {:identity "root"}))

这是有效的,因为在编译时,auth/cred-fn的值为。{1}} 被捕获是一个简单委托的功能 auth/another-fn。请注意,auth/another-fn尚未发生 评估了。现在在我们的测试中,Midje重新绑定auth/another-fn 引用模拟。然后它执行帖子,并在某处 在中间件中,auth/cred-fn被调用。内 auth/cred-fn,我们查找与auth/another-fn绑定的Var(其中 是我们的模拟),并调用它。当然,现在的行为是 正如你第一次想象的那样。

这个故事的寓意是,careful with the def in Clojure

答案 1 :(得分:0)

我仍然不确定为什么会这样,但我有一个解决方法。如果我将credential-fn替换为:

(defn another-fn
  []
  (println (str "hey look I got called"))
  (throw (Exception.)))

(defn cred-fn
  [creds]
  (another-fn))

然后在测试中为新函数创建一个假的,如下所示:

(fact "login with incorrect username and password returns unauthenticated"
  (:status (post "/login" invalid-auth-account-json)) => 401
  (provided
    (auth/another-fn) => nil))

(fact "login with correct username and password returns success"
  (:status (post "/login" auth-account-json)) => 200
  (provided
    (auth/another-fn) => {:identity "root"}))

我得到了我期待的结果。 cred-fn仍会被调用,但由于another-fn而无法调用provided

如果有人知道为什么会这样,我有兴趣知道。这可能是由于凭证函数被调用的方式? - https://github.com/marianoguerra/friend-json-workflow/blob/master/src/marianoguerra/friend_json_workflow.clj#L46