2016-07-01 4 views
12

У меня есть плагин проверки подлинности, и я хочу проверить свои контроллеры. Проблема заключается в том, что линия в этой вилке имеетКак использовать соединение с сеансом в 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 Authentication плагин

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 
+0

(Чтобы напечатать 'conn', используйте' IO.inspect conn'.) – Dogbert

+0

@Dogbert спасибо, я знаю :) Как решить мою проблему? – asiniy

+0

Можете ли вы разместить свой полный винт проверки подлинности? – TheAnh

ответ

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

+0

Я не думаю, что это хорошая идея. Я уже видел это решение. – asiniy

+0

Я бы сказал, что это стандартный способ решить эту проблему, которую вы найдете в большинстве мест. Я ожидаю, что манипулирование сеансом прямо так, как это может работать не так, как ожидалось, потому что сеансы обычно включают обратный переход на сервер и обратно, чтобы вступить в силу. – michalmuskala

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 

Там же подобный вопрос, как это:

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

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