【问题标题】:How to use connection with session in phoenix?如何在凤凰城中使用会话连接?
【发布时间】:2016-11-03 09:12:22
【问题描述】:

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

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

【问题讨论】:

  • (要打印conn,请使用IO.inspect conn。)
  • @Dogbert 谢谢我知道 :) 如何解决我的问题?
  • 你能发布你的完整身份验证插件吗?
  • @asiniy 你也可以添加router.ex 的内容吗?您能否将IO.inspect conn 的实际输出放在问题中?当前输出非常不可读,因为它都在一行上。 IO.inspect 应该会给你一个包装精美的输出。
  • @Dogbert 感谢IO.inspect 的提示。添加路由器

标签: elixir phoenix-framework ex-unit


【解决方案1】:

现在存在...

Plug Test init_test_session/2

conn = Plug.Test.init_test_session(conn, user_id: user.id)

【讨论】:

    【解决方案2】:

    通过在测试中完全绕过会话可以更容易地解决它。这个想法是在测试和身份验证插件中将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) 来验证连接。

    【讨论】:

    • 我认为这不是一个好主意。顺便说一句,我已经看到了那个解决方案。
    • 我想说这是解决这个问题的标准方法,你会在大多数地方找到。我希望像这样直接操作会话可能无法按预期工作,因为会话通常涉及到服务器的往返并返回生效。
    【解决方案3】:

    由于您在 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
    

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

    how can i set session in setup when i test phoenix action which need user_id in session?

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

    【讨论】:

      猜你喜欢
      • 2016-05-03
      • 2015-09-09
      • 2018-11-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-24
      • 1970-01-01
      • 2015-09-06
      相关资源
      最近更新 更多