130 lines
3.9 KiB
Elixir
130 lines
3.9 KiB
Elixir
defmodule MvWeb.JoinLiveTest do
|
|
@moduledoc """
|
|
Tests for the public join page (Subtask 4: Public join page and anti-abuse).
|
|
|
|
Covers: public path /join (unauthenticated 200), 404 when join disabled,
|
|
submit creates JoinRequest and shows success copy, honeypot prevents create,
|
|
rate limiting rejects excess submits. Uses unauthenticated conn; no User/Member.
|
|
"""
|
|
use MvWeb.ConnCase, async: true
|
|
import Phoenix.LiveViewTest
|
|
import Ecto.Query
|
|
|
|
alias Mv.Membership
|
|
alias Mv.Repo
|
|
|
|
describe "GET /join" do
|
|
@tag role: :unauthenticated
|
|
test "unauthenticated GET /join returns 200 when join form is enabled", %{conn: conn} do
|
|
enable_join_form(true)
|
|
conn = get(conn, "/join")
|
|
assert conn.status == 200
|
|
end
|
|
|
|
@tag role: :unauthenticated
|
|
test "unauthenticated GET /join returns 404 when join form is disabled", %{conn: conn} do
|
|
enable_join_form(false)
|
|
conn = get(conn, "/join")
|
|
assert conn.status == 404
|
|
end
|
|
end
|
|
|
|
describe "submit join form" do
|
|
setup :enable_join_form_for_test
|
|
|
|
@tag role: :unauthenticated
|
|
test "submit with valid allowlist data creates one JoinRequest and shows success copy", %{
|
|
conn: conn
|
|
} do
|
|
count_before = count_join_requests()
|
|
{:ok, view, _html} = live(conn, "/join")
|
|
|
|
view
|
|
|> form("#join-form", %{
|
|
"email" => "newuser#{System.unique_integer([:positive])}@example.com",
|
|
"first_name" => "Jane",
|
|
"last_name" => "Doe",
|
|
"honeypot" => ""
|
|
})
|
|
|> render_submit()
|
|
|
|
assert count_join_requests() == count_before + 1
|
|
assert view |> element("[data-testid='join-success-message']") |> has_element?()
|
|
assert render(view) =~ "saved your details"
|
|
assert render(view) =~ "click the link"
|
|
end
|
|
|
|
@tag role: :unauthenticated
|
|
test "submit with honeypot filled does not create JoinRequest but shows same success copy", %{
|
|
conn: conn
|
|
} do
|
|
count_before = count_join_requests()
|
|
{:ok, view, _html} = live(conn, "/join")
|
|
|
|
view
|
|
|> form("#join-form", %{
|
|
"email" => "bot#{System.unique_integer([:positive])}@example.com",
|
|
"first_name" => "Bot",
|
|
"last_name" => "User",
|
|
"honeypot" => "filled-by-bot"
|
|
})
|
|
|> render_submit()
|
|
|
|
assert count_join_requests() == count_before
|
|
assert view |> element("[data-testid='join-success-message']") |> has_element?()
|
|
end
|
|
|
|
@tag role: :unauthenticated
|
|
@tag :slow
|
|
test "after rate limit exceeded submit returns 429 or error and no new JoinRequest", %{
|
|
conn: conn
|
|
} do
|
|
enable_join_form(true)
|
|
# Rely on test config: join rate limit low (e.g. 2 per window)
|
|
base_email = "ratelimit#{System.unique_integer([:positive])}@example.com"
|
|
count_before = count_join_requests()
|
|
|
|
{:ok, view, _html} = live(conn, "/join")
|
|
|
|
# Exhaust limit with valid submits
|
|
for i <- 0..1 do
|
|
view
|
|
|> form("#join-form", %{
|
|
"email" => "#{i}-#{base_email}",
|
|
"first_name" => "User",
|
|
"last_name" => "Test",
|
|
"honeypot" => ""
|
|
})
|
|
|> render_submit()
|
|
end
|
|
|
|
# Next submit should be rate limited
|
|
result =
|
|
view
|
|
|> form("#join-form", %{
|
|
"email" => "third-#{base_email}",
|
|
"first_name" => "Third",
|
|
"last_name" => "User",
|
|
"honeypot" => ""
|
|
})
|
|
|> render_submit()
|
|
|
|
assert count_join_requests() == count_before + 2
|
|
assert result =~ "rate limit" or result =~ "too many" or result =~ "429"
|
|
end
|
|
end
|
|
|
|
defp enable_join_form(enabled) do
|
|
{:ok, settings} = Membership.get_settings()
|
|
{:ok, _} = Membership.update_settings(settings, %{join_form_enabled: enabled})
|
|
end
|
|
|
|
defp enable_join_form_for_test(_context) do
|
|
enable_join_form(true)
|
|
:ok
|
|
end
|
|
|
|
defp count_join_requests do
|
|
Repo.one(from j in "join_requests", select: count(j.id)) || 0
|
|
end
|
|
end
|