Fresh repository history for elektrine/tarakan hosted at https://git.elektrine.com/elektrine/tarakan.
207 lines
7.1 KiB
Elixir
207 lines
7.1 KiB
Elixir
defmodule TarakanWeb.ModerationLiveTest do
|
|
use TarakanWeb.ConnCase
|
|
|
|
import Phoenix.LiveViewTest
|
|
|
|
alias Tarakan.Accounts
|
|
alias Tarakan.Accounts.Scope
|
|
alias Tarakan.Moderation
|
|
|
|
test "reporting requires an authenticated account", %{conn: conn} do
|
|
assert {:error, {:redirect, %{to: "/accounts/log-in"}}} =
|
|
live(conn, ~p"/moderation/report")
|
|
end
|
|
|
|
test "a participant submits a restricted report and can inspect its case", %{conn: conn} do
|
|
reporter = account_fixture()
|
|
subject = account_fixture()
|
|
conn = log_in_account(conn, reporter)
|
|
|
|
{:ok, view, _html} =
|
|
live(conn, ~p"/moderation/report?subject_type=account&subject_id=#{subject.id}")
|
|
|
|
assert has_element?(view, "#moderation-report-form")
|
|
assert has_element?(view, "#header-report-content")
|
|
refute has_element?(view, "#header-moderation-queue")
|
|
|
|
view
|
|
|> form("#moderation-report-form",
|
|
report: %{
|
|
subject_type: "account",
|
|
subject_id: subject.id,
|
|
reason: "harassment",
|
|
description: "This account is posting targeted harassment in public review evidence."
|
|
}
|
|
)
|
|
|> render_submit()
|
|
|
|
{path, flash} = assert_redirect(view)
|
|
assert path =~ ~r{^/moderation/cases/\d+$}
|
|
assert flash["info"]
|
|
|
|
{:ok, case_view, _html} = live(conn, path)
|
|
|
|
assert has_element?(case_view, "#moderation-case")
|
|
assert has_element?(case_view, "#moderation-case-status", "Open")
|
|
refute has_element?(case_view, "#moderation-case-actions")
|
|
refute has_element?(case_view, "#moderation-case-assignment")
|
|
end
|
|
|
|
test "an ordinary account cannot open the moderator queue", %{conn: conn} do
|
|
conn = log_in_account(conn, account_fixture())
|
|
|
|
assert {:error, {:redirect, %{to: "/"}}} = live(conn, ~p"/moderation/queue")
|
|
end
|
|
|
|
test "a moderator assigns and resolves a queued report", %{conn: conn} do
|
|
case_record = report_account!(account_fixture(), account_fixture())
|
|
moderator = moderator_account_fixture()
|
|
conn = log_in_account(conn, moderator)
|
|
|
|
{:ok, queue_view, _html} = live(conn, ~p"/moderation/queue")
|
|
|
|
assert has_element?(queue_view, "#header-moderation-queue")
|
|
assert has_element?(queue_view, "#moderation-case-count", "1 open")
|
|
assert has_element?(queue_view, "#assign-moderation-case-#{case_record.id}")
|
|
|
|
queue_view
|
|
|> element("#assign-moderation-case-#{case_record.id}")
|
|
|> render_click()
|
|
|
|
assert has_element?(queue_view, "#cases-#{case_record.id}", "In review")
|
|
|
|
{:ok, case_view, _html} = live(conn, ~p"/moderation/cases/#{case_record.id}")
|
|
assert has_element?(case_view, "#moderation-case-decision-form")
|
|
|
|
case_view
|
|
|> form("#moderation-case-decision-form",
|
|
resolution: %{
|
|
reason: "The evidence was independently reviewed and confirms a policy violation."
|
|
}
|
|
)
|
|
|> render_submit(%{"disposition" => "resolved"})
|
|
|
|
assert has_element?(case_view, "#moderation-case-status", "Resolved")
|
|
assert has_element?(case_view, "#moderation-case-resolution")
|
|
refute has_element?(case_view, "#moderation-case-decision-form")
|
|
end
|
|
|
|
test "the subject can appeal without receiving participant identity data", %{conn: conn} do
|
|
reporter = account_fixture()
|
|
subject = account_fixture()
|
|
resolver = moderator_account_fixture()
|
|
|
|
case_record =
|
|
reporter
|
|
|> report_account!(subject)
|
|
|> resolve_case!(resolver, "resolved")
|
|
|
|
conn = log_in_account(conn, subject)
|
|
{:ok, view, _html} = live(conn, ~p"/moderation/cases/#{case_record.id}")
|
|
|
|
assert has_element?(view, "#moderation-appeal-form")
|
|
refute has_element?(view, "#moderation-case-actions")
|
|
|
|
view
|
|
|> form("#moderation-appeal-form",
|
|
appeal: %{
|
|
reason: "The decision cites evidence from a different account and should be reconsidered."
|
|
}
|
|
)
|
|
|> render_submit()
|
|
|
|
assert has_element?(view, "#moderation-case-appeals")
|
|
assert has_element?(view, "#moderation-case-appeals", "Open")
|
|
refute has_element?(view, "#moderation-appeal-form")
|
|
refute has_element?(view, "#moderation-case-actions")
|
|
end
|
|
|
|
test "an independent moderator decides an appeal", %{conn: conn} do
|
|
reporter = account_fixture()
|
|
subject = account_fixture()
|
|
resolver = moderator_account_fixture()
|
|
appeal_moderator = moderator_account_fixture()
|
|
|
|
case_record =
|
|
reporter
|
|
|> report_account!(subject)
|
|
|> resolve_case!(resolver, "resolved")
|
|
|
|
{:ok, appeal} =
|
|
Moderation.appeal(Accounts.scope_for_account(subject), case_record, %{
|
|
"reason" =>
|
|
"The original decision relied on unrelated evidence and needs independent review."
|
|
})
|
|
|
|
conn = log_in_account(conn, appeal_moderator)
|
|
{:ok, view, _html} = live(conn, ~p"/moderation/cases/#{case_record.id}")
|
|
|
|
assert has_element?(view, "#moderation-appeal-decision-form-#{appeal.id}")
|
|
|
|
view
|
|
|> form("#moderation-appeal-decision-form-#{appeal.id}",
|
|
appeal_decision: %{
|
|
appeal_id: appeal.id,
|
|
reason: "Independent review confirms the original evidence was unrelated."
|
|
}
|
|
)
|
|
|> render_submit(%{"decision" => "upheld"})
|
|
|
|
assert has_element?(view, "#moderation-case-status", "Overturned")
|
|
assert has_element?(view, "#moderation-appeal-#{appeal.id}", "Upheld")
|
|
refute has_element?(view, "#moderation-appeal-decision-form-#{appeal.id}")
|
|
end
|
|
|
|
test "an expired sudo session redirects resolution to re-authentication", %{conn: conn} do
|
|
case_record = report_account!(account_fixture(), account_fixture())
|
|
moderator = moderator_account_fixture()
|
|
|
|
{:ok, assigned} = Moderation.assign(Scope.for_account(moderator), case_record)
|
|
|
|
# Outside the two-hour sudo window.
|
|
stale_at = DateTime.add(DateTime.utc_now(), -3, :hour)
|
|
conn = log_in_account(conn, moderator, token_authenticated_at: stale_at)
|
|
{:ok, case_view, _html} = live(conn, ~p"/moderation/cases/#{assigned.id}")
|
|
|
|
assert has_element?(case_view, "#moderation-case-decision-form")
|
|
|
|
assert {:error,
|
|
{:live_redirect, %{to: "/accounts/log-in?return_to=%2Fmoderation%2Fcases%2F" <> _}}} =
|
|
case_view
|
|
|> form("#moderation-case-decision-form",
|
|
resolution: %{
|
|
reason: "The evidence was independently reviewed and confirms a violation."
|
|
}
|
|
)
|
|
|> render_submit(%{"disposition" => "resolved"})
|
|
|
|
{:ok, unresolved} = Moderation.get_case(Scope.for_account(moderator), assigned.id)
|
|
assert unresolved.status == "in_review"
|
|
end
|
|
|
|
defp report_account!(reporter, subject) do
|
|
{:ok, case_record} =
|
|
Moderation.report(Scope.for_account(reporter), %{
|
|
"subject_type" => "account",
|
|
"subject_id" => subject.id,
|
|
"reason" => "fabricated_evidence",
|
|
"description" => "The submitted evidence appears fabricated and needs independent review."
|
|
})
|
|
|
|
case_record
|
|
end
|
|
|
|
defp resolve_case!(case_record, moderator, disposition) do
|
|
{:ok, assigned} = Moderation.assign(Scope.for_account(moderator), case_record)
|
|
|
|
{:ok, decided} =
|
|
Moderation.resolve(
|
|
Scope.for_account(moderator),
|
|
assigned,
|
|
disposition,
|
|
"The evidence was independently reviewed and supports this moderation decision."
|
|
)
|
|
|
|
decided
|
|
end
|
|
end
|