Is it necessary to test role based access in every endpoint?

Hi. In my current project, I have different parts of my website that are available to different users. These users are anonymous, student, teacher or admin. I have plugs that limit access to endpoints based on these user roles. I have internal conflict as to whether I should test access denial of every user role in every endpoint’s integration tests. For instance, if I am busy testing an admin controller, should I test, for every endpoint, that all other users (that is, anonymous, student and teacher) cannot access the endpoints? In a broader sense, I am looking for advice on what to test and what not to test. I want to develop a thorough product but I do not want to waste time and effort on unnecessary tests. Thank you guys in advance for any help/advice/resources.

You might ask yourself a few questions:

  • Am I confident that the appropriate users have access to these endpoints?
  • Am I confident that when I make code changes in the future that the appropriate users will still have access to these endpoints?
  • Am I confident that when someone else makes code changes in the future that the appropriate users will still have access to these endpoints?

I typically have a hard time answering “yes” to all of these questions in my apps without test coverage. Additionally, the risk of showing someone a page that they should not see can make these types of controller tests even more worth the investment. They can be repetitive and boring, but if they are very similar, they can also often be dried up a bit with helper functions.

3 Likes

Thank you @baldwindavid. I appreciate the advice. It is reassuring to hear from other elixir users that they are also doing it. It gives me confidence to continue doing it. Thanks again!

@magosi - As an example of helper functions for this sort of thing, here is a naive implementation that tests that a 404 is returned for any roles that are not in the “allow” list for the given test case. The bottom two functions could be in ConnCase. In a single test at the top of each controller test file endpoints and allowed roles are listed. This could be changed in a lot of ways, but perhaps gives you some ideas as to making this easy on yourself.

test "all actions are only accessible by students and teachers", %{conn: conn} do
  # A list of endpoints wrapped in an anonymous function that receives the conn
  # as its argument
  endpoints = fn conn ->
    [
      get(conn, Routes.class_path(conn, :index)),
      get(conn, Routes.class_path(conn, :new)),
      post(conn, Routes.class_path(conn, :create, %{})),
      get(conn, Routes.class_path(conn, :show, "abc")),
      get(conn, Routes.class_path(conn, :edit, "abc")),
      put(conn, Routes.class_path(conn, :update, "abc", %{})),
      delete(conn, Routes.class_path(conn, :delete, "abc"))
    ]
  end

  # The helper function that declares which of our roles are "allowed". This
  # could be done as a "disallowed" list, but if you were to add a role in the
  # future, you would need to remember to add it in all the places where it needs
  # to be disallowed rather than allowed. It just seems safer as an allow list.
  assert_allowed_roles_for_endpoints(
    conn,
    [:student, :teacher],
    endpoints
  )
end

# These two functions would probably be in `ConnCase`
defp assert_allowed_roles_for_endpoints(conn, allowed_roles, endpoints) do
  # A list of all roles in our system. This likely pulls from somewhere else,
  # is displayed here for demonstration purposes.
  all_roles = [:anonymous, :student, :teacher]

  # Subtract the allowed roles to get the disallowed roles
  disallowed_roles = all_roles -- allowed_roles

  # For each disallowed role, create a test user and add it to the conn as the
  # current_user
  Enum.each(disallowed_roles, fn role ->
    conn
    # This assumes that your plug for setting current_user accommodates directly
    # assigning to the conn. This is a somewhat controversial technique.
    # The insert(role) also assumes that you have a factory method that takes a role
    # name to create a test user.
    |> assign(:current_user, insert(role))
    |> assert_disallowed_endpoints(endpoints)
  end)
end

defp assert_disallowed_endpoints(conn, endpoints) do
  Enum.each(
    # Call the endpoints list function with the conn
    endpoints.(conn),
    fn conn ->
      # For each endpoint check that a 404 status is set and that the conn is
      # halted. In your case, this might be a redirect or something.
      assert html_response(conn, 404)
      assert conn.halted
    end
  )
end
3 Likes