13

我有一个身份验证插件,我想测试我的控制器。问题是这个插头里的线有

user_id = get_session(conn, :user_id)

当我使用这种方法时它总是为零(我以前使用过脏黑客,但我不再想这样做了):

  @session  Plug.Session.init([
    store:            :cookie,
    key:              "_app",
    encryption_salt:  "secret",
    signing_salt:     "secret",
    encrypt:          false
  ])

user = MyApp.Factory.create(:user)

conn()
|> put_req_header("accept", "application/vnd.api+json")
|> put_req_header("content-type", "application/vnd.api+json")
|> Map.put(:secret_key_base, String.duplicate("abcdefgh", 8))
|> Plug.Session.call(@session)
|> fetch_session
|> put_session(:user_id, user.id)

我正在使用这个 conn 发送一个补丁请求,它的会话 user_id 是 nil。IO.puts conn在我的插件中的结果:

%Plug.Conn{adapter: {Plug.Adapters.Test.Conn, :...}, assigns: %{},
 before_send: [#Function<0.111117999/1 in Plug.Session.before_send/2>,
  #Function<0.110103833/1 in JaSerializer.ContentTypeNegotiation.set_content_type/2>,
  #Function<1.55011211/1 in Plug.Logger.call/2>,
  #Function<0.111117999/1 in Plug.Session.before_send/2>], body_params: %{},
 cookies: %{}, halted: false, host: "www.example.com", method: "PATCH",
 owner: #PID<0.349.0>,
 params: %{"data" => %{"attributes" => %{"action" => "start"}}, "id" => "245"},
 path_info: ["api", "tasks", "245"], peer: {{127, 0, 0, 1}, 111317}, port: 80,
 private: %{MyApp.Router => {[], %{}}, :phoenix_endpoint => MyApp.Endpoint,
   :phoenix_format => "json-api", :phoenix_pipelines => [:api],
   :phoenix_recycled => true,
   :phoenix_route => #Function<4.15522358/1 in MyApp.Router.match_route/4>,
   :phoenix_router => MyApp.Router, :plug_session => %{},
   :plug_session_fetch => :done, :plug_session_info => :write,
   :plug_skip_csrf_protection => true}, query_params: %{}, query_string: "",
 remote_ip: {127, 0, 0, 1}, req_cookies: %{},
 req_headers: [{"accept", "application/vnd.api+json"},
  {"content-type", "application/vnd.api+json"}], request_path: "/api/tasks/245",
 resp_body: nil, resp_cookies: %{},
 resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"},
  {"x-request-id", "d00tun3s9d7fo2ah2klnhafvt3ks4pbj"}], scheme: :http,
 script_name: [],
 secret_key_base: "npvJ1fWodIYzJ2eNnJmC5b1LecCTsveK4/mj7akuBaLdeAr2KGH4gwohwHsz8Ony",
 state: :unset, status: nil}

我需要做些什么来解决这个问题并很好地测试身份验证?

更新认证插件

defmodule MyApp.Plug.Authenticate do
  import Plug.Conn
  import Phoenix.Controller

  def init(default), do: default

  def call(conn, _) do
    IO.puts inspect get_session(conn, :user_id)
    IO.puts conn
    user_id = get_session(conn, :user_id)

    if user_id do
      current_user = MyApp.Repo.get(MyApp.Task, user_id)
      assign(conn, :current_user, current_user)
    else
      conn
      |> put_status(401)
      |> json(%{})
      |> halt
    end
  end
end

路由器(我从这里剪掉了一些部分):

defmodule MyApp.Router do
  use MyApp.Web, :router

  pipeline :api do
    plug :accepts, ["json-api"] # this line and 3 below are under JaSerializer package responsibility
    plug JaSerializer.ContentTypeNegotiation
    plug JaSerializer.Deserializer
    plug :fetch_session
    plug MyApp.Plug.Authenticate # this one
  end

  scope "/api", MyApp do
    pipe_through :api

    # tasks
    resources "/tasks", TaskController, only: [:show, :update]
  end
end
4

3 回答 3

3

现在存在...

插件测试 init_test_session/2

conn = Plug.Test.init_test_session(conn, user_id: user.id)
于 2018-04-13T12:45:07.197 回答
1

通过在测试中完全绕过会话可以更容易地解决它。这个想法是current_user在测试和身份验证插件中直接分配给 conn - 在current_user设置分配时跳过从会话中获取用户。这显然使身份验证插件本身未经测试,但在那里进行测试应该比通过整个堆栈更容易。

# in the authentication plug
def call(%{assigns: %{current_user: user}} = conn, opts) when user != nil do
  conn
end
def call(conn, opts) do
  # handle fetching user from session
end

这使您可以只assign(conn, :current_user, user)在测试中对连接进行身份验证。

于 2016-07-03T18:25:56.193 回答
1

由于您之前调用了会话,fetch_session/2因此在您的身份验证插件get_session/2中将返回nil

让我们更改您的身份验证插件以进行测试:

defmodule MyApp.Plug.Authenticate do
  import Plug.Conn
  import Phoenix.Controller
  alias MyApp.{Repo, User}

  def init(opts), do: opts

  def call(conn, _opts) do
    if user = get_user(conn) do
      assign(conn, :current_user, user)
    else
      conn
      |> put_status(401)
      |> put_flash(:error, "You must be logged in!")
      |> halt
    end
  end

  def get_user(conn) do
    case conn.assigns[:current_user] do
      nil ->
        case get_session(conn, :user_id) do
          id -> fetch_user(id)
          nil -> nil
        end
      user -> user
    end
  end

  defp fetch_user(id), do: Repo.get!(User, id)
end

现在您可以像这样测试您的插件:

defmodule MyApp.Plug.AuthenticateTest do
  use ExUnit.Case, async: true
  use Plug.Test
  import Phoenix.ConnTest
  alias MyApp.Plug.Authenticate

  @endpoint MyApp.Endpoint

  @session  Plug.Session.init([
    store:            :cookie,
    key:              "_app",
    encryption_salt:  "secret",
    signing_salt:     "secret",
    encrypt:          false
  ])

  setup do
    user = MyApp.Factory.create(:user)

    conn = build_conn()
    |> put_req_header("accept", "application/vnd.api+json")
    |> put_req_header("content-type", "application/vnd.api+json")
    |> Map.put(:secret_key_base, String.duplicate("abcdefgh", 8))
    |> Plug.Session.call(@session)
    |> fetch_session
    |> put_session(:user_id, user.id)

    {:ok, conn: conn, user: user}
  end

  test "get_user returns where it is set in session", %{conn: conn, user: user} do
    assert Authenticate.get_user(conn) == user
  end
end

最后你可以像这样测试你的控制器:

setup do
    user = MyApp.Factory.create(:user)

    {:ok, user: user}
  end

  test "GET /login", %{user: user} do
    conn = build_conn()
    |> assign(:current_user, user)
    |> get("/login")

    assert html_response(conn, 200) =~ "Successfull login"
  end

有一个类似的问题是这样的:

当我在会话中测试需要 user_id 的凤凰动作时,如何在设置中设置会话?

当您希望用户注入测试时,有一个更好的方法是将其存储conn.private并从您的身份验证插件中的私有读取。你应该看看看看有什么变化。希望对您有所帮助!

于 2016-07-08T10:59:43.520 回答