Merge remote-tracking branch 'origin/main' into feature/member-overview-groups
This commit is contained in:
commit
6831ba046f
48 changed files with 3516 additions and 182 deletions
1
test/mv/membership/member_export_build_test.exs
Normal file
1
test/mv/membership/member_export_build_test.exs
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
1
test/mv/membership/members_pdf_test.exs
Normal file
1
test/mv/membership/members_pdf_test.exs
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
227
test/mv/statistics_test.exs
Normal file
227
test/mv/statistics_test.exs
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
defmodule Mv.StatisticsTest do
|
||||
@moduledoc """
|
||||
Tests for Mv.Statistics module (member and membership fee cycle statistics).
|
||||
"""
|
||||
use Mv.DataCase, async: true
|
||||
|
||||
require Ash.Query
|
||||
import Ash.Expr
|
||||
|
||||
alias Mv.Membership.Member
|
||||
alias Mv.Statistics
|
||||
alias Mv.MembershipFees
|
||||
alias Mv.MembershipFees.MembershipFeeCycle
|
||||
alias Mv.MembershipFees.MembershipFeeType
|
||||
|
||||
setup do
|
||||
actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: actor}
|
||||
end
|
||||
|
||||
defp create_fee_type(actor, attrs) do
|
||||
MembershipFeeType
|
||||
|> Ash.Changeset.for_create(
|
||||
:create,
|
||||
Map.merge(
|
||||
%{
|
||||
name: "Test Fee #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("50.00"),
|
||||
interval: :yearly
|
||||
},
|
||||
attrs
|
||||
)
|
||||
)
|
||||
|> Ash.create!(actor: actor)
|
||||
end
|
||||
|
||||
describe "first_join_year/1" do
|
||||
test "returns the year of the earliest join_date", %{actor: actor} do
|
||||
Mv.Fixtures.member_fixture(%{join_date: ~D[2019-03-15]})
|
||||
Mv.Fixtures.member_fixture(%{join_date: ~D[2022-01-01]})
|
||||
assert Statistics.first_join_year(actor: actor) == 2019
|
||||
end
|
||||
|
||||
test "returns the only member's join year when one member exists", %{actor: actor} do
|
||||
Mv.Fixtures.member_fixture(%{join_date: ~D[2021-06-01]})
|
||||
assert Statistics.first_join_year(actor: actor) == 2021
|
||||
end
|
||||
|
||||
test "returns nil when no members exist", %{actor: actor} do
|
||||
# Guarantee empty member table so the assertion is deterministic
|
||||
Member
|
||||
|> Ash.read!(actor: actor)
|
||||
|> Enum.each(&Ash.destroy!(&1, actor: actor))
|
||||
|
||||
result = Statistics.first_join_year(actor: actor)
|
||||
assert is_nil(result)
|
||||
end
|
||||
end
|
||||
|
||||
describe "active_member_count/1" do
|
||||
test "returns 0 when there are no members", %{actor: actor} do
|
||||
assert Statistics.active_member_count(actor: actor) == 0
|
||||
end
|
||||
|
||||
test "returns 1 when one member has no exit_date", %{actor: actor} do
|
||||
Mv.Fixtures.member_fixture(%{join_date: ~D[2023-01-15]})
|
||||
assert Statistics.active_member_count(actor: actor) == 1
|
||||
end
|
||||
|
||||
test "returns 0 for that member when exit_date is set", %{actor: actor} do
|
||||
_member =
|
||||
Mv.Fixtures.member_fixture(%{join_date: ~D[2023-01-15], exit_date: ~D[2024-06-01]})
|
||||
|
||||
assert Statistics.active_member_count(actor: actor) == 0
|
||||
end
|
||||
|
||||
test "counts only active members when mix of active and inactive", %{actor: actor} do
|
||||
Mv.Fixtures.member_fixture(%{join_date: ~D[2022-01-01]})
|
||||
Mv.Fixtures.member_fixture(%{join_date: ~D[2023-01-01], exit_date: ~D[2024-01-01]})
|
||||
assert Statistics.active_member_count(actor: actor) == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "inactive_member_count/1" do
|
||||
test "returns 0 when all members are active", %{actor: actor} do
|
||||
Mv.Fixtures.member_fixture(%{join_date: ~D[2023-01-01]})
|
||||
assert Statistics.inactive_member_count(actor: actor) == 0
|
||||
end
|
||||
|
||||
test "returns 1 when one member has exit_date set", %{actor: actor} do
|
||||
Mv.Fixtures.member_fixture(%{join_date: ~D[2022-01-01], exit_date: ~D[2024-06-01]})
|
||||
assert Statistics.inactive_member_count(actor: actor) == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "joins_by_year/2" do
|
||||
test "returns 0 for year with no joins", %{actor: actor} do
|
||||
assert Statistics.joins_by_year(1999, actor: actor) == 0
|
||||
end
|
||||
|
||||
test "returns 1 when one member has join_date in that year", %{actor: actor} do
|
||||
Mv.Fixtures.member_fixture(%{join_date: ~D[2023-06-15]})
|
||||
assert Statistics.joins_by_year(2023, actor: actor) == 1
|
||||
end
|
||||
|
||||
test "returns 2 when two members joined in that year", %{actor: actor} do
|
||||
Mv.Fixtures.member_fixture(%{join_date: ~D[2023-01-01]})
|
||||
Mv.Fixtures.member_fixture(%{join_date: ~D[2023-12-31]})
|
||||
assert Statistics.joins_by_year(2023, actor: actor) == 2
|
||||
end
|
||||
end
|
||||
|
||||
describe "exits_by_year/2" do
|
||||
test "returns 0 for year with no exits", %{actor: actor} do
|
||||
assert Statistics.exits_by_year(1999, actor: actor) == 0
|
||||
end
|
||||
|
||||
test "returns 1 when one member has exit_date in that year", %{actor: actor} do
|
||||
Mv.Fixtures.member_fixture(%{join_date: ~D[2020-01-01], exit_date: ~D[2023-06-15]})
|
||||
assert Statistics.exits_by_year(2023, actor: actor) == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "cycle_totals_by_year/2" do
|
||||
test "returns zero totals for year with no cycles", %{actor: actor} do
|
||||
result = Statistics.cycle_totals_by_year(1999, actor: actor)
|
||||
assert result.total == Decimal.new(0)
|
||||
assert result.paid == Decimal.new(0)
|
||||
assert result.unpaid == Decimal.new(0)
|
||||
assert result.suspended == Decimal.new(0)
|
||||
end
|
||||
|
||||
test "returns totals by status for cycles in that year", %{actor: actor} do
|
||||
fee_type = create_fee_type(actor, %{amount: Decimal.new("50.00")})
|
||||
|
||||
# Creating members with fee type triggers cycle generation (2020..today). We use 2024 cycles.
|
||||
_member1 =
|
||||
Mv.Fixtures.member_fixture(%{
|
||||
join_date: ~D[2020-01-01],
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
|
||||
_member2 =
|
||||
Mv.Fixtures.member_fixture(%{
|
||||
join_date: ~D[2020-01-01],
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
|
||||
# Get 2024 cycles and set status (each member has one 2024 yearly cycle from generator)
|
||||
cycles_2024 =
|
||||
MembershipFeeCycle
|
||||
|> Ash.Query.filter(
|
||||
expr(cycle_start >= ^~D[2024-01-01] and cycle_start < ^~D[2025-01-01])
|
||||
)
|
||||
|> Ash.read!(actor: actor)
|
||||
|> Enum.sort_by(& &1.member_id)
|
||||
|
||||
[c1, c2] = cycles_2024
|
||||
assert {:ok, _} = Ash.update(c1, %{status: :paid}, domain: MembershipFees, actor: actor)
|
||||
|
||||
assert {:ok, _} =
|
||||
Ash.update(c2, %{status: :suspended}, domain: MembershipFees, actor: actor)
|
||||
|
||||
result = Statistics.cycle_totals_by_year(2024, actor: actor)
|
||||
assert Decimal.equal?(result.total, Decimal.new("100.00"))
|
||||
assert Decimal.equal?(result.paid, Decimal.new("50.00"))
|
||||
assert Decimal.equal?(result.unpaid, Decimal.new(0))
|
||||
assert Decimal.equal?(result.suspended, Decimal.new("50.00"))
|
||||
end
|
||||
|
||||
test "when fee_type_id is passed in opts, returns only cycles of that fee type", %{
|
||||
actor: actor
|
||||
} do
|
||||
fee_type_a = create_fee_type(actor, %{amount: Decimal.new("30.00")})
|
||||
fee_type_b = create_fee_type(actor, %{amount: Decimal.new("70.00")})
|
||||
|
||||
_m1 =
|
||||
Mv.Fixtures.member_fixture(%{
|
||||
join_date: ~D[2020-01-01],
|
||||
membership_fee_type_id: fee_type_a.id
|
||||
})
|
||||
|
||||
_m2 =
|
||||
Mv.Fixtures.member_fixture(%{
|
||||
join_date: ~D[2020-01-01],
|
||||
membership_fee_type_id: fee_type_b.id
|
||||
})
|
||||
|
||||
# Without filter: both fee types' cycles (2024)
|
||||
all_result = Statistics.cycle_totals_by_year(2024, actor: actor)
|
||||
assert Decimal.equal?(all_result.total, Decimal.new("100.00"))
|
||||
|
||||
# With fee_type_id as string (as from form/URL): only that type's cycles
|
||||
opts_a = [actor: actor, fee_type_id: to_string(fee_type_a.id)]
|
||||
result_a = Statistics.cycle_totals_by_year(2024, opts_a)
|
||||
assert Decimal.equal?(result_a.total, Decimal.new("30.00"))
|
||||
|
||||
opts_b = [actor: actor, fee_type_id: to_string(fee_type_b.id)]
|
||||
result_b = Statistics.cycle_totals_by_year(2024, opts_b)
|
||||
assert Decimal.equal?(result_b.total, Decimal.new("70.00"))
|
||||
end
|
||||
end
|
||||
|
||||
describe "open_amount_total/1" do
|
||||
test "returns 0 when there are no unpaid cycles", %{actor: actor} do
|
||||
assert Statistics.open_amount_total(actor: actor) == Decimal.new(0)
|
||||
end
|
||||
|
||||
test "returns sum of amount for all unpaid cycles", %{actor: actor} do
|
||||
fee_type = create_fee_type(actor, %{amount: Decimal.new("50.00")})
|
||||
|
||||
_member =
|
||||
Mv.Fixtures.member_fixture(%{
|
||||
join_date: ~D[2020-01-01],
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
|
||||
# Cycle generator creates yearly cycles (2020..today), all unpaid by default
|
||||
unpaid_sum = Statistics.open_amount_total(actor: actor)
|
||||
assert Decimal.compare(unpaid_sum, Decimal.new(0)) == :gt
|
||||
# Should be 50 * number of years from 2020 to current year
|
||||
current_year = Date.utc_today().year
|
||||
expected_count = current_year - 2020 + 1
|
||||
assert Decimal.equal?(unpaid_sum, Decimal.new(50 * expected_count))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -25,12 +25,13 @@ defmodule MvWeb.SidebarAuthorizationTest do
|
|||
end
|
||||
|
||||
describe "sidebar menu with admin user" do
|
||||
test "shows Members, Fee Types and Administration with all subitems" do
|
||||
test "shows Members, Fee Types, Statistics and Administration with all subitems" do
|
||||
user = Fixtures.user_with_role_fixture("admin")
|
||||
html = render_sidebar(sidebar_assigns(user))
|
||||
|
||||
assert html =~ ~s(href="/members")
|
||||
assert html =~ ~s(href="/membership_fee_types")
|
||||
assert html =~ ~s(href="/statistics")
|
||||
assert html =~ ~s(data-testid="sidebar-administration")
|
||||
assert html =~ ~s(href="/users")
|
||||
assert html =~ ~s(href="/groups")
|
||||
|
|
@ -41,11 +42,12 @@ defmodule MvWeb.SidebarAuthorizationTest do
|
|||
end
|
||||
|
||||
describe "sidebar menu with read_only user (Vorstand/Buchhaltung)" do
|
||||
test "shows Members and Groups (from Administration)" do
|
||||
test "shows Members, Statistics and Groups (from Administration)" do
|
||||
user = Fixtures.user_with_role_fixture("read_only")
|
||||
html = render_sidebar(sidebar_assigns(user))
|
||||
|
||||
assert html =~ ~s(href="/members")
|
||||
assert html =~ ~s(href="/statistics")
|
||||
assert html =~ ~s(href="/groups")
|
||||
end
|
||||
|
||||
|
|
@ -61,11 +63,12 @@ defmodule MvWeb.SidebarAuthorizationTest do
|
|||
end
|
||||
|
||||
describe "sidebar menu with normal_user (Kassenwart)" do
|
||||
test "shows Members and Groups" do
|
||||
test "shows Members, Statistics and Groups" do
|
||||
user = Fixtures.user_with_role_fixture("normal_user")
|
||||
html = render_sidebar(sidebar_assigns(user))
|
||||
|
||||
assert html =~ ~s(href="/members")
|
||||
assert html =~ ~s(href="/statistics")
|
||||
assert html =~ ~s(href="/groups")
|
||||
end
|
||||
|
||||
|
|
@ -88,10 +91,11 @@ defmodule MvWeb.SidebarAuthorizationTest do
|
|||
refute html =~ ~s(href="/members")
|
||||
end
|
||||
|
||||
test "does not show Fee Types or Administration" do
|
||||
test "does not show Statistics, Fee Types or Administration" do
|
||||
user = Fixtures.user_with_role_fixture("own_data")
|
||||
html = render_sidebar(sidebar_assigns(user))
|
||||
|
||||
refute html =~ ~s(href="/statistics")
|
||||
refute html =~ ~s(href="/membership_fee_types")
|
||||
refute html =~ ~s(href="/users")
|
||||
refute html =~ ~s(data-testid="sidebar-administration")
|
||||
|
|
|
|||
78
test/mv_web/live/statistics_live_test.exs
Normal file
78
test/mv_web/live/statistics_live_test.exs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
defmodule MvWeb.StatisticsLiveTest do
|
||||
@moduledoc """
|
||||
Tests for the Statistics LiveView at /statistics.
|
||||
|
||||
Uses explicit auth: conn is authenticated with a role that has access to
|
||||
the statistics page (read_only by default; override with @tag :role).
|
||||
"""
|
||||
use MvWeb.ConnCase, async: true
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
alias Mv.MembershipFees.MembershipFeeType
|
||||
|
||||
describe "statistics page" do
|
||||
@describetag role: :read_only
|
||||
test "renders statistics page with title and key labels for authenticated user with access",
|
||||
%{
|
||||
conn: conn
|
||||
} do
|
||||
{:ok, _view, html} = live(conn, ~p"/statistics")
|
||||
|
||||
assert html =~ "Statistics"
|
||||
assert html =~ "Active members"
|
||||
assert html =~ "Unpaid"
|
||||
assert html =~ "Contributions by year"
|
||||
assert html =~ "Member numbers by year"
|
||||
end
|
||||
|
||||
test "page shows overview of all relevant years without year selector", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, ~p"/statistics")
|
||||
|
||||
# No year dropdown: single select for year should not be present as main control
|
||||
assert html =~ "Overview" or html =~ "overview"
|
||||
# table header or legend
|
||||
assert html =~ "Year"
|
||||
end
|
||||
|
||||
test "fee_type_id in URL updates selected filter and contributions", %{conn: conn} do
|
||||
actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
fee_types =
|
||||
MembershipFeeType
|
||||
|> Ash.Query.sort(name: :asc)
|
||||
|> Ash.read!(domain: Mv.MembershipFees, actor: actor)
|
||||
|
||||
fee_type =
|
||||
case List.first(fee_types) do
|
||||
nil ->
|
||||
MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "Test Fee #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("50.00"),
|
||||
interval: :yearly
|
||||
})
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
ft ->
|
||||
ft
|
||||
end
|
||||
|
||||
path = ~p"/statistics" <> "?" <> URI.encode_query(%{"fee_type_id" => fee_type.id})
|
||||
{:ok, view, html} = live(conn, path)
|
||||
|
||||
assert view |> element("select#fee-type-filter") |> has_element?()
|
||||
assert html =~ fee_type.name
|
||||
assert html =~ "Contributions by year"
|
||||
end
|
||||
end
|
||||
|
||||
describe "statistics page with own_data role" do
|
||||
@describetag role: :member
|
||||
test "redirects when user has only own_data (no access to statistics page)", %{conn: conn} do
|
||||
# member role uses own_data permission set; /statistics is not in own_data pages
|
||||
conn = get(conn, ~p"/statistics")
|
||||
assert redirected_to(conn) != ~p"/statistics"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -522,7 +522,7 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "export to CSV" do
|
||||
describe "export dropdown" do
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
|
|
@ -535,34 +535,139 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
%{member1: m1}
|
||||
end
|
||||
|
||||
test "export button is rendered when no selection and shows (all)", %{conn: conn} do
|
||||
test "export dropdown button is rendered when no selection and shows (all)", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
|
||||
# Dropdown button should be present
|
||||
assert html =~ ~s(data-testid="export-dropdown")
|
||||
assert html =~ ~s(data-testid="export-dropdown-button")
|
||||
assert html =~ "Export"
|
||||
# Button text shows "all" when 0 selected (locale-dependent)
|
||||
assert html =~ "Export to CSV"
|
||||
assert html =~ "all" or html =~ "All"
|
||||
end
|
||||
|
||||
test "after select_member event export button shows (1)", %{conn: conn, member1: member1} do
|
||||
test "after select_member event export dropdown shows (1)", %{conn: conn, member1: member1} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members")
|
||||
|
||||
render_click(view, "select_member", %{"id" => member1.id})
|
||||
|
||||
html = render(view)
|
||||
assert html =~ "Export to CSV"
|
||||
assert html =~ "Export"
|
||||
assert html =~ "(1)"
|
||||
end
|
||||
|
||||
test "form has correct action and payload hidden input", %{conn: conn} do
|
||||
test "dropdown opens and closes on click", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/members")
|
||||
{:ok, view, _html} = live(conn, "/members")
|
||||
|
||||
# Initially closed
|
||||
refute has_element?(view, ~s([data-testid="export-dropdown-menu"]))
|
||||
|
||||
# Click to open
|
||||
view
|
||||
|> element(~s([data-testid="export-dropdown-button"]))
|
||||
|> render_click()
|
||||
|
||||
# Menu should be visible
|
||||
assert has_element?(view, ~s([data-testid="export-dropdown-menu"]))
|
||||
|
||||
# Click to close
|
||||
view
|
||||
|> element(~s([data-testid="export-dropdown-button"]))
|
||||
|> render_click()
|
||||
|
||||
# Menu should be hidden
|
||||
refute has_element?(view, ~s([data-testid="export-dropdown-menu"]))
|
||||
end
|
||||
|
||||
test "dropdown has click-away and ESC handlers", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members")
|
||||
|
||||
# Open dropdown
|
||||
view
|
||||
|> element(~s([data-testid="export-dropdown-button"]))
|
||||
|> render_click()
|
||||
|
||||
html = render(view)
|
||||
assert has_element?(view, ~s([data-testid="export-dropdown-menu"]))
|
||||
|
||||
# Check that click-away handler is present
|
||||
assert html =~ ~s(phx-click-away="close_dropdown")
|
||||
# Check that ESC handler is present
|
||||
assert html =~ ~s(phx-window-keydown="close_dropdown")
|
||||
assert html =~ ~s(phx-key="Escape")
|
||||
end
|
||||
|
||||
test "dropdown menu contains CSV and PDF export links with correct payload", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members")
|
||||
|
||||
# Open dropdown
|
||||
view
|
||||
|> element(~s([data-testid="export-dropdown-button"]))
|
||||
|> render_click()
|
||||
|
||||
html = render(view)
|
||||
|
||||
# Check CSV link
|
||||
assert html =~ ~s(data-testid="export-csv-link")
|
||||
assert html =~ "/members/export.csv"
|
||||
assert html =~ ~s(name="payload")
|
||||
assert html =~ ~s(type="hidden")
|
||||
assert html =~ ~s(name="_csrf_token")
|
||||
|
||||
# Check PDF link
|
||||
assert html =~ ~s(data-testid="export-pdf-link")
|
||||
assert html =~ "/members/export.pdf"
|
||||
assert html =~ ~s(name="payload")
|
||||
assert html =~ ~s(type="hidden")
|
||||
assert html =~ ~s(name="_csrf_token")
|
||||
|
||||
# Both forms should have the same payload
|
||||
csv_form_payload = extract_payload_from_form(html, "/members/export.csv")
|
||||
pdf_form_payload = extract_payload_from_form(html, "/members/export.pdf")
|
||||
|
||||
assert csv_form_payload == pdf_form_payload
|
||||
assert csv_form_payload != nil
|
||||
end
|
||||
|
||||
test "dropdown has correct ARIA attributes", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members")
|
||||
|
||||
html = render(view)
|
||||
|
||||
# Button should have aria-haspopup="menu"
|
||||
assert html =~ ~s(aria-haspopup="menu")
|
||||
# Button should have aria-expanded="false" when closed
|
||||
assert html =~ ~s(aria-expanded="false")
|
||||
# Button should have aria-controls pointing to menu
|
||||
assert html =~ ~s(aria-controls="export-dropdown-menu")
|
||||
|
||||
# Open dropdown
|
||||
view
|
||||
|> element(~s([data-testid="export-dropdown-button"]))
|
||||
|> render_click()
|
||||
|
||||
html = render(view)
|
||||
# Button should have aria-expanded="true" when open
|
||||
assert html =~ ~s(aria-expanded="true")
|
||||
# Menu should have role="menu"
|
||||
assert html =~ ~s(role="menu")
|
||||
end
|
||||
|
||||
# Helper to extract payload value from form HTML
|
||||
defp extract_payload_from_form(html, action_path) do
|
||||
case Regex.run(
|
||||
~r/<form[^>]*action="#{Regex.escape(action_path)}"[^>]*>.*?<input[^>]*name="payload"[^>]*value="([^"]+)"/s,
|
||||
html
|
||||
) do
|
||||
[_, payload] -> payload
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -107,6 +107,37 @@ defmodule MvWeb.Plugs.CheckPagePermissionTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "statistics route /statistics" do
|
||||
test "read_only can access /statistics" do
|
||||
user = Fixtures.user_with_role_fixture("read_only")
|
||||
conn = conn_with_user("/statistics", user) |> CheckPagePermission.call([])
|
||||
|
||||
refute conn.halted
|
||||
end
|
||||
|
||||
test "normal_user can access /statistics" do
|
||||
user = Fixtures.user_with_role_fixture("normal_user")
|
||||
conn = conn_with_user("/statistics", user) |> CheckPagePermission.call([])
|
||||
|
||||
refute conn.halted
|
||||
end
|
||||
|
||||
test "admin can access /statistics" do
|
||||
user = Fixtures.user_with_role_fixture("admin")
|
||||
conn = conn_with_user("/statistics", user) |> CheckPagePermission.call([])
|
||||
|
||||
refute conn.halted
|
||||
end
|
||||
|
||||
test "own_data cannot access /statistics" do
|
||||
user = Fixtures.user_with_role_fixture("own_data")
|
||||
conn = conn_with_user("/statistics", user) |> CheckPagePermission.call([])
|
||||
|
||||
assert conn.halted
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "read_only and normal_user denied on admin routes" do
|
||||
test "read_only cannot access /admin/roles" do
|
||||
user = Fixtures.user_with_role_fixture("read_only")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue