Skip to content

Instantly share code, notes, and snippets.

@mmcc
Last active October 26, 2023 00:05
Show Gist options
  • Save mmcc/b8768508181859a8618a162ac03d7b2a to your computer and use it in GitHub Desktop.
Save mmcc/b8768508181859a8618a162ac03d7b2a to your computer and use it in GitHub Desktop.
Elixir test macros
defmodule AppWeb.AuthConnCase do
alias AppWeb.Router
use ExUnit.CaseTemplate
import Phoenix.ConnTest, only: [dispatch: 5, json_response: 2]
@doc """
Allows you to call the same set of tests with the same describe block
Setups is an array of named setups, i.e. [first_setup: [:setup_conn, :thing], second_setup: [:different_setup, :neato_setup]]
## Examples
defmodule MultiAuthTest do
use ExUnit.Case, async: true
describe_multi "Something works with both cookies and api keys",
[cookie_auth: [:create_user, :cookie_authed_connection],
token_auth: [:create_api_key, :api_key_authed_connection]] do
test "authed conn is authed", %{conn: conn} do
# ...
end
end
end
"""
defmacro describe_multi(message, setups, do: block) do
for {name, setup} <- setups do
quote do
describe "#{unquote(message)} (#{unquote(name)})" do
setup unquote(setup)
unquote(block)
end
end
end
end
@doc """
Simple macros to test whether or not a route is authenticated.
## Examples
defmodule AuthenticatedTest do
use ExUnit.Case, async: true
@path_helper :authenticated_path
test_authentication_required_for(:get, @path_helper, :index, [], interface_only: true)
test_authentication_required_for(:post, @path_helper, :create, [])
test_authentication_required_for(:delete, @path_helper, :delete, [123])
end
"""
defmacro test_authentication_required_for(method, path_name, action, additional_path_args \\ [], options \\ []) do
%{interface_only: interface_only} = Enum.into(options, %{interface_only: false})
auth_test = quote do
test "#{unquote(action)} requires authentication", %{conn: conn} do
method = unquote(method)
path_name = unquote(path_name)
action = unquote(action)
args = unquote(additional_path_args)
options = unquote(options)
assert make_unauthenticated_request(conn, @endpoint, method, path_name, action, args)
end
end
interface_only_test = interface_only and quote do
test "#{unquote(action)} requires cookie authentication", %{conn: conn} do
method = unquote(method)
path_name = unquote(path_name)
action = unquote(action)
args = unquote(additional_path_args)
options = unquote(options)
assert make_access_token_authenticated_request(conn, @endpoint, method, path_name, action, args)
end
end
[auth_test, interface_only_test]
end
def make_unauthenticated_request(conn, endpoint, method, path_name, action, additional_args) do
args = [conn, action] ++ additional_args
path = apply(Router.Helpers, path_name, args)
dispatch(conn, endpoint, method, path, nil) |> json_response(401)
end
def make_access_token_authenticated_request(conn, endpoint, method, path_name, action, additional_args) do
args = [conn, action] ++ additional_args
user = Factory(:user)
token_params = %{
name: "Some Key",
permissions: ["data:read", "video:read", "video:write"]
}
{:ok, token} = AppWeb.User.create_access_token(user, token_params)
auth_string = "#{token.id}:#{token.private_key}" |> Base.encode64
conn = Plug.Conn.put_req_header(conn, "authorization", "Basic #{auth_string}")
path = apply(Router.Helpers, path_name, args)
resp = dispatch(conn, endpoint, method, path, nil) |> json_response(401)
resp["error"]["messages"] |> hd() =~ "dashboard interface"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment