zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

test.ex (8498B)


      1 defmodule Plug.Test do
      2   @moduledoc """
      3   Conveniences for testing plugs.
      4 
      5   This module can be used in your test cases, like this:
      6 
      7       use ExUnit.Case, async: true
      8       use Plug.Test
      9 
     10   Using this module will:
     11 
     12     * import all the functions from this module
     13     * import all the functions from the `Plug.Conn` module
     14 
     15   By default, Plug tests checks for invalid header keys, e.g. header keys which
     16   include uppercase letters, and raises a `Plug.Conn.InvalidHeaderError` when
     17   it finds one. To disable it, set `:validate_header_keys_during_test` to
     18   false on the app config.
     19 
     20       config :plug, :validate_header_keys_during_test, false
     21 
     22   """
     23 
     24   @doc false
     25   defmacro __using__(_) do
     26     quote do
     27       import Plug.Test
     28       import Plug.Conn
     29     end
     30   end
     31 
     32   alias Plug.Conn
     33   @typep params :: binary | list | map | nil
     34 
     35   @doc """
     36   Creates a test connection.
     37 
     38   The request `method` and `path` are required arguments. `method` may be any
     39   value that implements `to_string/1` and it will be properly converted and
     40   normalized (e.g., `:get` or `"post"`).
     41 
     42   The `path` is commonly the request path with optional query string but it may
     43   also be a complete URI. When a URI is given, the host and schema will be used
     44   as part of the request too.
     45 
     46   The `params_or_body` field must be one of:
     47 
     48   * `nil` - meaning there is no body;
     49   * a binary - containing a request body. For such cases, `:headers`
     50     must be given as option with a content-type;
     51   * a map or list - containing the parameters which will automatically
     52     set the content-type to multipart. The map or list may contain
     53     other lists or maps and all entries will be normalized to string
     54     keys;
     55 
     56   ## Examples
     57 
     58       conn(:get, "/foo?bar=10")
     59       conn(:get, "/foo", %{bar: 10})
     60       conn(:post, "/")
     61       conn("patch", "/", "") |> put_req_header("content-type", "application/json")
     62 
     63   """
     64   @spec conn(String.Chars.t(), binary, params) :: Conn.t()
     65   def conn(method, path, params_or_body \\ nil) do
     66     Plug.Adapters.Test.Conn.conn(%Plug.Conn{}, method, path, params_or_body)
     67   end
     68 
     69   @doc """
     70   Returns the sent response.
     71 
     72   This function is useful when the code being invoked crashes and
     73   there is a need to verify a particular response was sent, even with
     74   the crash. It returns a tuple with `{status, headers, body}`.
     75   """
     76   def sent_resp(%Conn{adapter: {Plug.Adapters.Test.Conn, %{ref: ref}}}) do
     77     case receive_resp(ref) do
     78       :no_resp ->
     79         raise "no sent response available for the given connection. " <>
     80                 "Maybe the application did not send anything?"
     81 
     82       response ->
     83         case receive_resp(ref) do
     84           :no_resp ->
     85             send(self(), {ref, response})
     86             response
     87 
     88           _otherwise ->
     89             raise "a response for the given connection has been sent more than once"
     90         end
     91     end
     92   end
     93 
     94   defp receive_resp(ref) do
     95     receive do
     96       {^ref, response} -> response
     97     after
     98       0 -> :no_resp
     99     end
    100   end
    101 
    102   @doc """
    103   Returns the informational requests that have been sent.
    104 
    105   This function depends on gathering the messages sent by the test adapter when
    106   informational messages, such as an early hint, are sent. Calling this
    107   function will clear the informational request messages from the inbox for the
    108   process.  To assert on multiple informs, the result of the function should be
    109   stored in a variable.
    110 
    111   ## Examples
    112 
    113       conn = conn(:get, "/foo", "bar=10")
    114       informs = Plug.Test.sent_informs(conn)
    115       assert {"/static/application.css", [{"accept", "text/css"}]} in informs
    116       assert {"/static/application.js", [{"accept", "application/javascript"}]} in informs
    117 
    118   """
    119   def sent_informs(%Conn{adapter: {Plug.Adapters.Test.Conn, %{ref: ref}}}) do
    120     Enum.reverse(receive_informs(ref, []))
    121   end
    122 
    123   defp receive_informs(ref, informs) do
    124     receive do
    125       {^ref, :inform, response} ->
    126         receive_informs(ref, [response | informs])
    127     after
    128       0 -> informs
    129     end
    130   end
    131 
    132   @doc """
    133   Returns the upgrade requests that have been sent.
    134 
    135   This function depends on gathering the messages sent by the test adapter when
    136   upgrade requests are sent. Calling this function will clear the upgrade request messages from the inbox for the
    137   process.
    138 
    139   ## Examples
    140 
    141       conn = conn(:get, "/foo", "bar=10")
    142       upgrades = Plug.Test.send_upgrades(conn)
    143       assert {:websocket, [opt: :value]} in upgrades
    144 
    145   """
    146   def sent_upgrades(%Conn{adapter: {Plug.Adapters.Test.Conn, %{ref: ref}}}) do
    147     Enum.reverse(receive_upgrades(ref, []))
    148   end
    149 
    150   defp receive_upgrades(ref, upgrades) do
    151     receive do
    152       {^ref, :upgrade, response} ->
    153         receive_upgrades(ref, [response | upgrades])
    154     after
    155       0 -> upgrades
    156     end
    157   end
    158 
    159   @doc """
    160   Returns the assets that have been pushed.
    161 
    162   This function depends on gathering the messages sent by the test adapter
    163   when assets are pushed. Calling this function will clear the pushed message
    164   from the inbox for the process. To assert on multiple pushes, the result
    165   of the function should be stored in a variable.
    166 
    167   ## Examples
    168 
    169       conn = conn(:get, "/foo?bar=10")
    170       pushes = Plug.Test.sent_pushes(conn)
    171       assert {"/static/application.css", [{"accept", "text/css"}]} in pushes
    172       assert {"/static/application.js", [{"accept", "application/javascript"}]} in pushes
    173 
    174   """
    175   @deprecated "Most browsers and clients have removed push support"
    176   def sent_pushes(%Conn{adapter: {Plug.Adapters.Test.Conn, %{ref: ref}}}) do
    177     Enum.reverse(receive_pushes(ref, []))
    178   end
    179 
    180   defp receive_pushes(ref, pushes) do
    181     receive do
    182       {^ref, :push, response} ->
    183         receive_pushes(ref, [response | pushes])
    184     after
    185       0 -> pushes
    186     end
    187   end
    188 
    189   @doc """
    190   Puts the HTTP protocol.
    191   """
    192   def put_http_protocol(conn, http_protocol) do
    193     update_in(conn.adapter, fn {adapter, payload} ->
    194       {adapter, Map.put(payload, :http_protocol, http_protocol)}
    195     end)
    196   end
    197 
    198   @doc """
    199   Puts the peer data.
    200   """
    201   def put_peer_data(conn, peer_data) do
    202     update_in(conn.adapter, fn {adapter, payload} ->
    203       {adapter, Map.put(payload, :peer_data, peer_data)}
    204     end)
    205   end
    206 
    207   @doc """
    208   Puts a request cookie.
    209   """
    210   @spec put_req_cookie(Conn.t(), binary, binary) :: Conn.t()
    211   def put_req_cookie(conn, key, value) when is_binary(key) and is_binary(value) do
    212     conn = delete_req_cookie(conn, key)
    213     %{conn | req_headers: [{"cookie", "#{key}=#{value}"} | conn.req_headers]}
    214   end
    215 
    216   @doc """
    217   Deletes a request cookie.
    218   """
    219   @spec delete_req_cookie(Conn.t(), binary) :: Conn.t()
    220   def delete_req_cookie(%Conn{req_cookies: %Plug.Conn.Unfetched{}} = conn, key)
    221       when is_binary(key) do
    222     key = "#{key}="
    223     size = byte_size(key)
    224     fun = &match?({"cookie", value} when binary_part(value, 0, size) == key, &1)
    225     %{conn | req_headers: Enum.reject(conn.req_headers, fun)}
    226   end
    227 
    228   def delete_req_cookie(_conn, key) when is_binary(key) do
    229     raise ArgumentError, message: "cannot put/delete request cookies after cookies were fetched"
    230   end
    231 
    232   @doc """
    233   Moves cookies from a connection into a new connection for subsequent requests.
    234 
    235   This function copies the cookie information in `old_conn` into `new_conn`,
    236   emulating multiple requests done by clients where cookies are always passed
    237   forward, and returns the new version of `new_conn`.
    238   """
    239   @spec recycle_cookies(Conn.t(), Conn.t()) :: Conn.t()
    240   def recycle_cookies(new_conn, old_conn) do
    241     req_cookies = Plug.Conn.fetch_cookies(old_conn).req_cookies
    242 
    243     resp_cookies =
    244       Enum.reduce(old_conn.resp_cookies, req_cookies, fn {key, opts}, acc ->
    245         if value = Map.get(opts, :value) do
    246           Map.put(acc, key, value)
    247         else
    248           Map.delete(acc, key)
    249         end
    250       end)
    251 
    252     Enum.reduce(resp_cookies, new_conn, fn {key, value}, acc ->
    253       put_req_cookie(acc, key, value)
    254     end)
    255   end
    256 
    257   @doc """
    258   Initializes the session with the given contents.
    259 
    260   If the session has already been initialized, the new contents will be merged
    261   with the previous ones.
    262   """
    263   @spec init_test_session(Conn.t(), %{optional(String.t() | atom) => any}) :: Conn.t()
    264   def init_test_session(conn, session) do
    265     conn =
    266       if conn.private[:plug_session_fetch] do
    267         Conn.fetch_session(conn)
    268       else
    269         conn
    270         |> Conn.put_private(:plug_session, %{})
    271         |> Conn.put_private(:plug_session_fetch, :done)
    272       end
    273 
    274     Enum.reduce(session, conn, fn {key, value}, conn ->
    275       Conn.put_session(conn, key, value)
    276     end)
    277   end
    278 end