Как использовать соединение с сеансом в phoenix?

У меня есть плагин проверки подлинности, и я хочу проверить свои контроллеры. Проблема в том, что строка в этой вилке имеет

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, и его session_id пользователя равен нулю. Результаты 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}

Что мне нужно сделать для решения этой проблемы и проверки подлинности?

UPDATE. Подключить аутентификацию

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

router (я отрубил некоторые части отсюда):

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

Ответ 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) в тестах для аутентификации соединения.

Ответ 2

Поскольку вы вызываете свой сеанс до 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

Есть аналогичный вопрос:

как установить сессию в настройке, когда я тестирую действие phoenix, которое нужно user_id в сеансе?

И лучше, когда вы хотите, чтобы пользовательский для теста загружал его в conn.private и читал его из приватного в вашем подключаемом модуле проверки подлинности. Вы должны взглянуть на изменение. Надеюсь, что вам помогут!