Merge remote-tracking branch 'origin/main' into feature/ui-for-adding-members-groups
This commit is contained in:
commit
03f27a5938
33 changed files with 2765 additions and 501 deletions
|
|
@ -95,9 +95,10 @@ defmodule Mv.Accounts.UserPoliciesTest do
|
|||
test "can update own email", %{user: user} do
|
||||
new_email = "updated#{System.unique_integer([:positive])}@example.com"
|
||||
|
||||
# Non-admins use :update (email only); :update_user is admin-only (member link/unlink).
|
||||
{:ok, updated_user} =
|
||||
user
|
||||
|> Ash.Changeset.for_update(:update_user, %{email: new_email})
|
||||
|> Ash.Changeset.for_update(:update, %{email: new_email})
|
||||
|> Ash.update(actor: user)
|
||||
|
||||
assert updated_user.email == Ash.CiString.new(new_email)
|
||||
|
|
@ -118,7 +119,7 @@ defmodule Mv.Accounts.UserPoliciesTest do
|
|||
test "cannot update other users (returns forbidden)", %{user: user, other_user: other_user} do
|
||||
assert_raise Ash.Error.Forbidden, fn ->
|
||||
other_user
|
||||
|> Ash.Changeset.for_update(:update_user, %{email: "hacked@example.com"})
|
||||
|> Ash.Changeset.for_update(:update, %{email: "hacked@example.com"})
|
||||
|> Ash.update!(actor: user)
|
||||
end
|
||||
end
|
||||
|
|
@ -163,9 +164,10 @@ defmodule Mv.Accounts.UserPoliciesTest do
|
|||
test "can update own email", %{user: user} do
|
||||
new_email = "updated#{System.unique_integer([:positive])}@example.com"
|
||||
|
||||
# Non-admins use :update (email only); :update_user is admin-only (member link/unlink).
|
||||
{:ok, updated_user} =
|
||||
user
|
||||
|> Ash.Changeset.for_update(:update_user, %{email: new_email})
|
||||
|> Ash.Changeset.for_update(:update, %{email: new_email})
|
||||
|> Ash.update(actor: user)
|
||||
|
||||
assert updated_user.email == Ash.CiString.new(new_email)
|
||||
|
|
@ -186,7 +188,7 @@ defmodule Mv.Accounts.UserPoliciesTest do
|
|||
test "cannot update other users (returns forbidden)", %{user: user, other_user: other_user} do
|
||||
assert_raise Ash.Error.Forbidden, fn ->
|
||||
other_user
|
||||
|> Ash.Changeset.for_update(:update_user, %{email: "hacked@example.com"})
|
||||
|> Ash.Changeset.for_update(:update, %{email: "hacked@example.com"})
|
||||
|> Ash.update!(actor: user)
|
||||
end
|
||||
end
|
||||
|
|
@ -231,9 +233,10 @@ defmodule Mv.Accounts.UserPoliciesTest do
|
|||
test "can update own email", %{user: user} do
|
||||
new_email = "updated#{System.unique_integer([:positive])}@example.com"
|
||||
|
||||
# Non-admins use :update (email only); :update_user is admin-only (member link/unlink).
|
||||
{:ok, updated_user} =
|
||||
user
|
||||
|> Ash.Changeset.for_update(:update_user, %{email: new_email})
|
||||
|> Ash.Changeset.for_update(:update, %{email: new_email})
|
||||
|> Ash.update(actor: user)
|
||||
|
||||
assert updated_user.email == Ash.CiString.new(new_email)
|
||||
|
|
@ -254,7 +257,7 @@ defmodule Mv.Accounts.UserPoliciesTest do
|
|||
test "cannot update other users (returns forbidden)", %{user: user, other_user: other_user} do
|
||||
assert_raise Ash.Error.Forbidden, fn ->
|
||||
other_user
|
||||
|> Ash.Changeset.for_update(:update_user, %{email: "hacked@example.com"})
|
||||
|> Ash.Changeset.for_update(:update, %{email: "hacked@example.com"})
|
||||
|> Ash.update!(actor: user)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -127,8 +127,10 @@ defmodule Mv.Authorization.PermissionSetsTest do
|
|||
test "includes correct pages" do
|
||||
permissions = PermissionSets.get_permissions(:own_data)
|
||||
|
||||
assert "/" in permissions.pages
|
||||
assert "/profile" in permissions.pages
|
||||
# Root "/" is not allowed for own_data (Mitglied is redirected to profile)
|
||||
refute "/" in permissions.pages
|
||||
# Profile is at /users/:id, not a separate /profile route
|
||||
assert "/users/:id" in permissions.pages
|
||||
assert "/members/:id" in permissions.pages
|
||||
end
|
||||
end
|
||||
|
|
@ -229,7 +231,7 @@ defmodule Mv.Authorization.PermissionSetsTest do
|
|||
permissions = PermissionSets.get_permissions(:read_only)
|
||||
|
||||
assert "/" in permissions.pages
|
||||
assert "/profile" in permissions.pages
|
||||
assert "/users/:id" in permissions.pages
|
||||
assert "/members" in permissions.pages
|
||||
assert "/members/:id" in permissions.pages
|
||||
assert "/custom_field_values" in permissions.pages
|
||||
|
|
@ -333,7 +335,7 @@ defmodule Mv.Authorization.PermissionSetsTest do
|
|||
permissions = PermissionSets.get_permissions(:normal_user)
|
||||
|
||||
assert "/" in permissions.pages
|
||||
assert "/profile" in permissions.pages
|
||||
assert "/users/:id" in permissions.pages
|
||||
assert "/members" in permissions.pages
|
||||
assert "/members/new" in permissions.pages
|
||||
assert "/members/:id" in permissions.pages
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
defmodule Mv.Membership.Import.MemberCSVTest do
|
||||
use Mv.DataCase, async: false
|
||||
use Mv.DataCase, async: true
|
||||
|
||||
alias Mv.Membership.Import.MemberCSV
|
||||
|
||||
|
|
@ -35,11 +35,10 @@ defmodule Mv.Membership.Import.MemberCSVTest do
|
|||
end
|
||||
|
||||
describe "prepare/2" do
|
||||
test "function exists and accepts file_content and opts" do
|
||||
test "accepts file_content and opts and returns tagged tuple" do
|
||||
file_content = "email\njohn@example.com"
|
||||
opts = []
|
||||
|
||||
# This will fail until the function is implemented
|
||||
result = MemberCSV.prepare(file_content, opts)
|
||||
assert match?({:ok, _}, result) or match?({:error, _}, result)
|
||||
end
|
||||
|
|
@ -65,11 +64,6 @@ defmodule Mv.Membership.Import.MemberCSVTest do
|
|||
|
||||
assert {:error, _reason} = MemberCSV.prepare(file_content, opts)
|
||||
end
|
||||
|
||||
test "function has documentation" do
|
||||
# Check that @doc exists by reading the module
|
||||
assert function_exported?(MemberCSV, :prepare, 2)
|
||||
end
|
||||
end
|
||||
|
||||
describe "process_chunk/4" do
|
||||
|
|
@ -78,7 +72,7 @@ defmodule Mv.Membership.Import.MemberCSVTest do
|
|||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
test "function exists and accepts chunk_rows_with_lines, column_map, custom_field_map, and opts",
|
||||
test "accepts chunk_rows_with_lines, column_map, custom_field_map, and opts and returns tagged tuple",
|
||||
%{
|
||||
actor: actor
|
||||
} do
|
||||
|
|
@ -87,7 +81,6 @@ defmodule Mv.Membership.Import.MemberCSVTest do
|
|||
custom_field_map = %{}
|
||||
opts = [actor: actor]
|
||||
|
||||
# This will fail until the function is implemented
|
||||
result = MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
|
||||
assert match?({:ok, _}, result) or match?({:error, _}, result)
|
||||
end
|
||||
|
|
@ -231,7 +224,11 @@ defmodule Mv.Membership.Import.MemberCSVTest do
|
|||
custom_field_map = %{to_string(custom_field.id) => 1}
|
||||
|
||||
custom_field_lookup = %{
|
||||
to_string(custom_field.id) => %{id: custom_field.id, value_type: custom_field.value_type}
|
||||
to_string(custom_field.id) => %{
|
||||
id: custom_field.id,
|
||||
value_type: custom_field.value_type,
|
||||
name: custom_field.name
|
||||
}
|
||||
}
|
||||
|
||||
opts = [custom_field_lookup: custom_field_lookup, actor: actor]
|
||||
|
|
@ -332,11 +329,6 @@ defmodule Mv.Membership.Import.MemberCSVTest do
|
|||
assert chunk_result.errors == []
|
||||
end
|
||||
|
||||
test "function has documentation" do
|
||||
# Check that @doc exists by reading the module
|
||||
assert function_exported?(MemberCSV, :process_chunk, 4)
|
||||
end
|
||||
|
||||
test "error capping collects exactly 50 errors", %{actor: actor} do
|
||||
# Create 50 rows with invalid emails
|
||||
chunk_rows_with_lines =
|
||||
|
|
@ -611,15 +603,300 @@ defmodule Mv.Membership.Import.MemberCSVTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "module documentation" do
|
||||
test "module has @moduledoc" do
|
||||
# Check that the module exists and has documentation
|
||||
assert Code.ensure_loaded?(MemberCSV)
|
||||
describe "custom field import" do
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
# Try to get the module documentation
|
||||
{:docs_v1, _, _, _, %{"en" => moduledoc}, _, _} = Code.fetch_docs(MemberCSV)
|
||||
assert is_binary(moduledoc)
|
||||
assert String.length(moduledoc) > 0
|
||||
test "creates member with valid integer custom field value", %{actor: actor} do
|
||||
# Create integer custom field
|
||||
{:ok, custom_field} =
|
||||
Mv.Membership.CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "Alter",
|
||||
value_type: :integer
|
||||
})
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
chunk_rows_with_lines = [
|
||||
{2,
|
||||
%{
|
||||
member: %{email: "withage@example.com"},
|
||||
custom: %{to_string(custom_field.id) => "25"}
|
||||
}}
|
||||
]
|
||||
|
||||
column_map = %{email: 0}
|
||||
custom_field_map = %{to_string(custom_field.id) => 1}
|
||||
|
||||
custom_field_lookup = %{
|
||||
to_string(custom_field.id) => %{
|
||||
id: custom_field.id,
|
||||
value_type: custom_field.value_type,
|
||||
name: custom_field.name
|
||||
}
|
||||
}
|
||||
|
||||
opts = [custom_field_lookup: custom_field_lookup, actor: actor]
|
||||
|
||||
assert {:ok, chunk_result} =
|
||||
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
|
||||
|
||||
assert chunk_result.inserted == 1
|
||||
assert chunk_result.failed == 0
|
||||
|
||||
# Verify member and custom field value were created
|
||||
members = Mv.Membership.list_members!(actor: actor)
|
||||
member = Enum.find(members, &(&1.email == "withage@example.com"))
|
||||
assert member != nil
|
||||
|
||||
{:ok, member_with_cf} = Ash.load(member, :custom_field_values, actor: actor)
|
||||
assert length(member_with_cf.custom_field_values) == 1
|
||||
cfv = List.first(member_with_cf.custom_field_values)
|
||||
assert cfv.custom_field_id == custom_field.id
|
||||
assert cfv.value.value == 25
|
||||
assert cfv.value.type == :integer
|
||||
end
|
||||
|
||||
test "returns error for invalid integer custom field value", %{actor: actor} do
|
||||
# Create integer custom field
|
||||
{:ok, custom_field} =
|
||||
Mv.Membership.CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "Alter",
|
||||
value_type: :integer
|
||||
})
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
chunk_rows_with_lines = [
|
||||
{2,
|
||||
%{
|
||||
member: %{email: "invalidage@example.com"},
|
||||
custom: %{to_string(custom_field.id) => "abc"}
|
||||
}}
|
||||
]
|
||||
|
||||
column_map = %{email: 0}
|
||||
custom_field_map = %{to_string(custom_field.id) => 1}
|
||||
|
||||
custom_field_lookup = %{
|
||||
to_string(custom_field.id) => %{
|
||||
id: custom_field.id,
|
||||
value_type: custom_field.value_type,
|
||||
name: custom_field.name
|
||||
}
|
||||
}
|
||||
|
||||
opts = [custom_field_lookup: custom_field_lookup, actor: actor]
|
||||
|
||||
assert {:ok, chunk_result} =
|
||||
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
|
||||
|
||||
assert chunk_result.inserted == 0
|
||||
assert chunk_result.failed == 1
|
||||
assert length(chunk_result.errors) == 1
|
||||
|
||||
error = List.first(chunk_result.errors)
|
||||
assert error.csv_line_number == 2
|
||||
assert error.message =~ "custom_field: Alter"
|
||||
assert error.message =~ "Number"
|
||||
assert error.message =~ "abc"
|
||||
end
|
||||
|
||||
test "returns error for invalid date custom field value", %{actor: actor} do
|
||||
# Create date custom field
|
||||
{:ok, custom_field} =
|
||||
Mv.Membership.CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "Geburtstag",
|
||||
value_type: :date
|
||||
})
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
chunk_rows_with_lines = [
|
||||
{3,
|
||||
%{
|
||||
member: %{email: "invaliddate@example.com"},
|
||||
custom: %{to_string(custom_field.id) => "not-a-date"}
|
||||
}}
|
||||
]
|
||||
|
||||
column_map = %{email: 0}
|
||||
custom_field_map = %{to_string(custom_field.id) => 1}
|
||||
|
||||
custom_field_lookup = %{
|
||||
to_string(custom_field.id) => %{
|
||||
id: custom_field.id,
|
||||
value_type: custom_field.value_type,
|
||||
name: custom_field.name
|
||||
}
|
||||
}
|
||||
|
||||
opts = [custom_field_lookup: custom_field_lookup, actor: actor]
|
||||
|
||||
assert {:ok, chunk_result} =
|
||||
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
|
||||
|
||||
assert chunk_result.inserted == 0
|
||||
assert chunk_result.failed == 1
|
||||
assert length(chunk_result.errors) == 1
|
||||
|
||||
error = List.first(chunk_result.errors)
|
||||
assert error.csv_line_number == 3
|
||||
assert error.message =~ "custom_field: Geburtstag"
|
||||
assert error.message =~ "Date"
|
||||
end
|
||||
|
||||
test "returns error for invalid email custom field value", %{actor: actor} do
|
||||
# Create email custom field
|
||||
{:ok, custom_field} =
|
||||
Mv.Membership.CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "Work Email",
|
||||
value_type: :email
|
||||
})
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
chunk_rows_with_lines = [
|
||||
{4,
|
||||
%{
|
||||
member: %{email: "invalidemailcf@example.com"},
|
||||
custom: %{to_string(custom_field.id) => "not-an-email"}
|
||||
}}
|
||||
]
|
||||
|
||||
column_map = %{email: 0}
|
||||
custom_field_map = %{to_string(custom_field.id) => 1}
|
||||
|
||||
custom_field_lookup = %{
|
||||
to_string(custom_field.id) => %{
|
||||
id: custom_field.id,
|
||||
value_type: custom_field.value_type,
|
||||
name: custom_field.name
|
||||
}
|
||||
}
|
||||
|
||||
opts = [custom_field_lookup: custom_field_lookup, actor: actor]
|
||||
|
||||
assert {:ok, chunk_result} =
|
||||
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
|
||||
|
||||
assert chunk_result.inserted == 0
|
||||
assert chunk_result.failed == 1
|
||||
assert length(chunk_result.errors) == 1
|
||||
|
||||
error = List.first(chunk_result.errors)
|
||||
assert error.csv_line_number == 4
|
||||
assert error.message =~ "custom_field: Work Email"
|
||||
assert error.message =~ "E-Mail"
|
||||
end
|
||||
|
||||
test "returns error for invalid boolean custom field value", %{actor: actor} do
|
||||
# Create boolean custom field
|
||||
{:ok, custom_field} =
|
||||
Mv.Membership.CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "Is Active",
|
||||
value_type: :boolean
|
||||
})
|
||||
|> Ash.create(actor: actor)
|
||||
|
||||
chunk_rows_with_lines = [
|
||||
{5,
|
||||
%{
|
||||
member: %{email: "invalidbool@example.com"},
|
||||
custom: %{to_string(custom_field.id) => "maybe"}
|
||||
}}
|
||||
]
|
||||
|
||||
column_map = %{email: 0}
|
||||
custom_field_map = %{to_string(custom_field.id) => 1}
|
||||
|
||||
custom_field_lookup = %{
|
||||
to_string(custom_field.id) => %{
|
||||
id: custom_field.id,
|
||||
value_type: custom_field.value_type,
|
||||
name: custom_field.name
|
||||
}
|
||||
}
|
||||
|
||||
opts = [custom_field_lookup: custom_field_lookup, actor: actor]
|
||||
|
||||
assert {:ok, chunk_result} =
|
||||
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
|
||||
|
||||
assert chunk_result.inserted == 0
|
||||
assert chunk_result.failed == 1
|
||||
assert length(chunk_result.errors) == 1
|
||||
|
||||
error = List.first(chunk_result.errors)
|
||||
assert error.csv_line_number == 5
|
||||
assert error.message =~ "custom_field: Is Active"
|
||||
# Error message should indicate boolean/Yes-No validation failure
|
||||
assert String.contains?(error.message, "Yes/No") ||
|
||||
String.contains?(error.message, "true/false") ||
|
||||
String.contains?(error.message, "boolean")
|
||||
end
|
||||
end
|
||||
|
||||
describe "prepare/2 with custom fields" do
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create a custom field
|
||||
{:ok, custom_field} =
|
||||
Mv.Membership.CustomField
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
name: "Membership Number",
|
||||
value_type: :string
|
||||
})
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
%{actor: system_actor, custom_field: custom_field}
|
||||
end
|
||||
|
||||
test "includes custom field in custom_field_map when header matches", %{
|
||||
custom_field: custom_field
|
||||
} do
|
||||
# CSV with custom field column
|
||||
csv_content = "email;Membership Number\njohn@example.com;12345"
|
||||
|
||||
assert {:ok, import_state} = MemberCSV.prepare(csv_content)
|
||||
|
||||
# Check that custom field is mapped
|
||||
assert Map.has_key?(import_state.custom_field_map, to_string(custom_field.id))
|
||||
assert import_state.column_map[:email] == 0
|
||||
end
|
||||
|
||||
test "includes warning for unknown custom field column", %{custom_field: _custom_field} do
|
||||
# CSV with unknown custom field column (not matching any existing custom field)
|
||||
csv_content = "email;NichtExistierend\njohn@example.com;value"
|
||||
|
||||
assert {:ok, import_state} = MemberCSV.prepare(csv_content)
|
||||
|
||||
# Check that warning is present
|
||||
assert import_state.warnings != []
|
||||
warning = List.first(import_state.warnings)
|
||||
assert warning =~ "NichtExistierend"
|
||||
assert warning =~ "ignored"
|
||||
assert warning =~ "custom field"
|
||||
|
||||
# Check that unknown column is not in custom_field_map
|
||||
assert import_state.custom_field_map == %{}
|
||||
# Member import should still succeed
|
||||
assert import_state.column_map[:email] == 0
|
||||
end
|
||||
|
||||
test "import succeeds even with unknown custom field columns", %{custom_field: _custom_field} do
|
||||
# CSV with unknown custom field column
|
||||
csv_content = "email;UnknownField\njohn@example.com;value"
|
||||
|
||||
assert {:ok, import_state} = MemberCSV.prepare(csv_content)
|
||||
|
||||
# Import state should be valid
|
||||
assert import_state.column_map[:email] == 0
|
||||
assert import_state.chunks != []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -183,6 +183,39 @@ defmodule MvWeb.AuthorizationTest do
|
|||
assert Authorization.can_access_page?(read_only_user, "/members/123/edit") == false
|
||||
end
|
||||
|
||||
test "read_only can access own profile /users/:id only" do
|
||||
read_only_user = %{
|
||||
id: "read-only-123",
|
||||
role: %{permission_set_name: "read_only"}
|
||||
}
|
||||
|
||||
assert Authorization.can_access_page?(read_only_user, "/users/read-only-123") == true
|
||||
assert Authorization.can_access_page?(read_only_user, "/users/read-only-123/edit") == true
|
||||
assert Authorization.can_access_page?(read_only_user, "/users/other-id") == false
|
||||
assert Authorization.can_access_page?(read_only_user, "/users/other-id/edit") == false
|
||||
end
|
||||
|
||||
test "normal_user can access own profile /users/:id only" do
|
||||
normal_user = %{
|
||||
id: "normal-456",
|
||||
role: %{permission_set_name: "normal_user"}
|
||||
}
|
||||
|
||||
assert Authorization.can_access_page?(normal_user, "/users/normal-456") == true
|
||||
assert Authorization.can_access_page?(normal_user, "/users/normal-456/edit") == true
|
||||
assert Authorization.can_access_page?(normal_user, "/users/other-id") == false
|
||||
end
|
||||
|
||||
test "reserved segment 'new' is not matched by :id" do
|
||||
read_only_user = %{
|
||||
id: "read-only-123",
|
||||
role: %{permission_set_name: "read_only"}
|
||||
}
|
||||
|
||||
assert Authorization.can_access_page?(read_only_user, "/members/new") == false
|
||||
assert Authorization.can_access_page?(read_only_user, "/groups/new") == false
|
||||
end
|
||||
|
||||
test "returns false for nil user" do
|
||||
assert Authorization.can_access_page?(nil, "/members") == false
|
||||
assert Authorization.can_access_page?(nil, "/admin/roles") == false
|
||||
|
|
|
|||
|
|
@ -68,6 +68,11 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
end
|
||||
|
||||
describe "get_last_completed_cycle/2" do
|
||||
test "returns nil when member is nil" do
|
||||
assert MembershipFeeHelpers.get_last_completed_cycle(nil) == nil
|
||||
assert MembershipFeeHelpers.get_last_completed_cycle(nil, Date.utc_today()) == nil
|
||||
end
|
||||
|
||||
test "returns last completed cycle for member", %{actor: actor} do
|
||||
# Create test data
|
||||
fee_type =
|
||||
|
|
@ -184,6 +189,11 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|||
end
|
||||
|
||||
describe "get_current_cycle/2" do
|
||||
test "returns nil when member is nil" do
|
||||
assert MembershipFeeHelpers.get_current_cycle(nil) == nil
|
||||
assert MembershipFeeHelpers.get_current_cycle(nil, Date.utc_today()) == nil
|
||||
end
|
||||
|
||||
test "returns current cycle for member", %{actor: actor} do
|
||||
fee_type =
|
||||
Mv.MembershipFees.MembershipFeeType
|
||||
|
|
|
|||
73
test/mv_web/live/global_settings_live_config_test.exs
Normal file
73
test/mv_web/live/global_settings_live_config_test.exs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
defmodule MvWeb.GlobalSettingsLiveConfigTest do
|
||||
@moduledoc """
|
||||
Tests for GlobalSettingsLive that modify global Application configuration.
|
||||
|
||||
These tests run with `async: false` to prevent race conditions when
|
||||
modifying global Application environment variables (Application.put_env).
|
||||
This follows the same pattern as Mv.ConfigTest.
|
||||
"""
|
||||
use MvWeb.ConnCase, async: false
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
# Helper function to upload CSV file in tests
|
||||
defp upload_csv_file(view, csv_content, filename) do
|
||||
view
|
||||
|> file_input("#csv-upload-form", :csv_file, [
|
||||
%{
|
||||
last_modified: System.system_time(:second),
|
||||
name: filename,
|
||||
content: csv_content,
|
||||
size: byte_size(csv_content),
|
||||
type: "text/csv"
|
||||
}
|
||||
])
|
||||
|> render_upload(filename)
|
||||
end
|
||||
|
||||
describe "CSV Import - Configuration Tests" do
|
||||
setup %{conn: conn} do
|
||||
# Ensure admin user
|
||||
admin_user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||
conn = MvWeb.ConnCase.conn_with_password_user(conn, admin_user)
|
||||
|
||||
{:ok, conn: conn, admin_user: admin_user}
|
||||
end
|
||||
|
||||
test "configured row limit is enforced", %{conn: conn} do
|
||||
# Business rule: CSV import respects configured row limits
|
||||
# Test that a custom limit (500) is enforced, not just the default (1000)
|
||||
original_config = Application.get_env(:mv, :csv_import, [])
|
||||
|
||||
try do
|
||||
Application.put_env(:mv, :csv_import, max_rows: 500)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||
|
||||
# Generate CSV with 501 rows (exceeding custom limit of 500)
|
||||
header = "first_name;last_name;email;street;postal_code;city\n"
|
||||
|
||||
rows =
|
||||
for i <- 1..501 do
|
||||
"Row#{i};Last#{i};email#{i}@example.com;Street#{i};12345;City#{i}\n"
|
||||
end
|
||||
|
||||
large_csv = header <> Enum.join(rows)
|
||||
|
||||
# Simulate file upload using helper function
|
||||
upload_csv_file(view, large_csv, "too_many_rows_custom.csv")
|
||||
|
||||
view
|
||||
|> form("#csv-upload-form", %{})
|
||||
|> render_submit()
|
||||
|
||||
html = render(view)
|
||||
# Business rule: import should be rejected when exceeding configured limit
|
||||
assert html =~ "exceeds" or html =~ "maximum" or html =~ "limit" or
|
||||
html =~ "Failed to prepare"
|
||||
after
|
||||
# Restore original config
|
||||
Application.put_env(:mv, :csv_import, original_config)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -110,7 +110,7 @@ defmodule MvWeb.GlobalSettingsLiveTest do
|
|||
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||
|
||||
# Check for custom fields notice text
|
||||
assert html =~ "Custom fields" or html =~ "custom field"
|
||||
assert html =~ "Use the data field name"
|
||||
end
|
||||
|
||||
test "admin user sees template download links", %{conn: conn} do
|
||||
|
|
@ -158,15 +158,12 @@ defmodule MvWeb.GlobalSettingsLiveTest do
|
|||
end
|
||||
|
||||
test "non-admin user does not see import section", %{conn: conn} do
|
||||
# Create non-admin user (member role)
|
||||
# Member (own_data) is redirected when accessing /settings (no page permission)
|
||||
member_user = Mv.Fixtures.user_with_role_fixture("own_data")
|
||||
conn = MvWeb.ConnCase.conn_with_password_user(conn, member_user)
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||
|
||||
# Import section should not be visible
|
||||
refute html =~ "Import Members" or html =~ "CSV Import" or
|
||||
(html =~ "Import" and html =~ "CSV")
|
||||
assert {:error, {:redirect, %{to: to}}} = live(conn, ~p"/settings")
|
||||
assert to == "/users/#{member_user.id}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -236,15 +233,12 @@ defmodule MvWeb.GlobalSettingsLiveTest do
|
|||
end
|
||||
|
||||
test "non-admin cannot start import", %{conn: conn} do
|
||||
# Create non-admin user
|
||||
# Member (own_data) is redirected when accessing /settings (no page permission)
|
||||
member_user = Mv.Fixtures.user_with_role_fixture("own_data")
|
||||
conn = MvWeb.ConnCase.conn_with_password_user(conn, member_user)
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||
|
||||
# Since non-admin shouldn't see the section, we check that import section is not visible
|
||||
html = render(view)
|
||||
refute html =~ "Import Members" or html =~ "CSV Import" or html =~ "start_import"
|
||||
assert {:error, {:redirect, %{to: to}}} = live(conn, ~p"/settings")
|
||||
assert to == "/users/#{member_user.id}"
|
||||
end
|
||||
|
||||
test "invalid CSV shows user-friendly error", %{conn: conn} do
|
||||
|
|
|
|||
|
|
@ -11,17 +11,8 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
|
|||
require Ash.Query
|
||||
|
||||
setup %{conn: conn} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
# Create admin user
|
||||
{:ok, user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Changeset.for_create(:register_with_password, %{
|
||||
email: "admin#{System.unique_integer([:positive])}@mv.local",
|
||||
password: "testpassword123"
|
||||
})
|
||||
|> Ash.create(actor: system_actor)
|
||||
|
||||
# User must have admin role (or normal_user) to access /membership_fee_types pages
|
||||
user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||
authenticated_conn = conn_with_password_user(conn, user)
|
||||
%{conn: authenticated_conn, user: user}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ defmodule MvWeb.MembershipFeeTypeLive.IndexTest do
|
|||
|> Ash.create!(actor: admin_user)
|
||||
end
|
||||
|
||||
# Helper to create a member
|
||||
# Uses admin actor from global setup to ensure authorization; falls back to system_actor when nil
|
||||
# Helper to create a member. Requires actor (e.g. admin_user from setup); no fallback so
|
||||
# missing-actor bugs are not masked in tests.
|
||||
defp create_member(attrs, actor) do
|
||||
default_attrs = %{
|
||||
first_name: "Test",
|
||||
|
|
@ -39,8 +39,7 @@ defmodule MvWeb.MembershipFeeTypeLive.IndexTest do
|
|||
}
|
||||
|
||||
attrs = Map.merge(default_attrs, attrs)
|
||||
effective_actor = actor || Mv.Helpers.SystemActor.get_system_actor()
|
||||
{:ok, member} = Mv.Membership.create_member(attrs, actor: effective_actor)
|
||||
{:ok, member} = Mv.Membership.create_member(attrs, actor: actor)
|
||||
member
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ defmodule MvWeb.ProfileNavigationTest do
|
|||
|
||||
describe "profile navigation" do
|
||||
test "clicking profile button redirects to current user profile", %{conn: conn} do
|
||||
# Setup: Create and login a user
|
||||
user = create_test_user(%{email: "test@example.com"})
|
||||
# User needs a role with page permission for "/" (e.g. admin)
|
||||
user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||
conn = conn_with_password_user(conn, user)
|
||||
{:ok, view, _html} = live(conn, "/")
|
||||
|
||||
|
|
@ -21,9 +21,18 @@ defmodule MvWeb.ProfileNavigationTest do
|
|||
assert_redirected(view, "/users/#{user.id}")
|
||||
end
|
||||
|
||||
test "profile navigation shows correct user data", %{conn: conn} do
|
||||
# Setup: Create and login a user
|
||||
test "profile navigation shows correct user data", %{conn: conn, actor: actor} do
|
||||
# User with password (from create_test_user) and admin role so they can access "/"
|
||||
user = create_test_user(%{email: "test@example.com"})
|
||||
admin_role = Mv.Fixtures.role_fixture("admin")
|
||||
|
||||
{:ok, user} =
|
||||
user
|
||||
|> Ash.Changeset.for_update(:update, %{})
|
||||
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
user = Ash.load!(user, :role, domain: Mv.Accounts, actor: actor)
|
||||
conn = conn_with_password_user(conn, user)
|
||||
|
||||
# Navigate to profile
|
||||
|
|
@ -40,8 +49,8 @@ defmodule MvWeb.ProfileNavigationTest do
|
|||
|
||||
describe "sidebar" do
|
||||
test "renders profile button with correct attributes", %{conn: conn} do
|
||||
# Setup: Create and login a user
|
||||
user = create_test_user(%{email: "test@example.com"})
|
||||
# User needs a role with page permission for "/"
|
||||
user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||
conn = conn_with_password_user(conn, user)
|
||||
{:ok, _view, html} = live(conn, "/")
|
||||
|
||||
|
|
@ -85,16 +94,27 @@ defmodule MvWeb.ProfileNavigationTest do
|
|||
})
|
||||
|> Ash.create!(domain: Mv.Accounts, actor: actor)
|
||||
|
||||
# Assign role so user can access "/" (page permission)
|
||||
admin_role = Mv.Fixtures.role_fixture("admin")
|
||||
|
||||
{:ok, user_with_role} =
|
||||
user
|
||||
|> Ash.Changeset.for_update(:update, %{})
|
||||
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
user_with_role = Ash.load!(user_with_role, :role, domain: Mv.Accounts, actor: actor)
|
||||
|
||||
# Login user via OIDC
|
||||
conn = sign_in_user_via_oidc(conn, user)
|
||||
conn = sign_in_user_via_oidc(conn, user_with_role)
|
||||
|
||||
# Navigate to home and click profile
|
||||
{:ok, view, _html} = live(conn, "/")
|
||||
view |> element("a", "Profil") |> render_click()
|
||||
|
||||
# Verify we're on the correct profile page with OIDC specific information
|
||||
{:ok, _profile_view, html} = live(conn, "/users/#{user.id}")
|
||||
assert html =~ to_string(user.email)
|
||||
{:ok, _profile_view, html} = live(conn, "/users/#{user_with_role.id}")
|
||||
assert html =~ to_string(user_with_role.email)
|
||||
# Password auth should be disabled for OIDC users
|
||||
assert html =~ "Not enabled"
|
||||
end
|
||||
|
|
@ -103,14 +123,10 @@ defmodule MvWeb.ProfileNavigationTest do
|
|||
conn: conn,
|
||||
actor: actor
|
||||
} do
|
||||
# Create password user
|
||||
password_user =
|
||||
create_test_user(%{
|
||||
email: "password2@example.com",
|
||||
password: "test_password123"
|
||||
})
|
||||
# Users need a role with page permission for "/"
|
||||
password_user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||
|
||||
# Create OIDC user
|
||||
# Create OIDC user and assign admin role
|
||||
user_info = %{
|
||||
"sub" => "oidc_789",
|
||||
"preferred_username" => "oidc@example.com"
|
||||
|
|
@ -129,6 +145,17 @@ defmodule MvWeb.ProfileNavigationTest do
|
|||
})
|
||||
|> Ash.create!(domain: Mv.Accounts, actor: actor)
|
||||
|
||||
admin_role = Mv.Fixtures.role_fixture("admin")
|
||||
|
||||
{:ok, oidc_user_with_role} =
|
||||
oidc_user
|
||||
|> Ash.Changeset.for_update(:update, %{})
|
||||
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
oidc_user_with_role =
|
||||
Ash.load!(oidc_user_with_role, :role, domain: Mv.Accounts, actor: actor)
|
||||
|
||||
# Test with password user
|
||||
conn_password = conn_with_password_user(conn, password_user)
|
||||
{:ok, view_password, _html} = live(conn_password, "/")
|
||||
|
|
@ -136,16 +163,17 @@ defmodule MvWeb.ProfileNavigationTest do
|
|||
assert_redirected(view_password, "/users/#{password_user.id}")
|
||||
|
||||
# Test with OIDC user
|
||||
conn_oidc = sign_in_user_via_oidc(conn, oidc_user)
|
||||
conn_oidc = sign_in_user_via_oidc(conn, oidc_user_with_role)
|
||||
{:ok, view_oidc, _html} = live(conn_oidc, "/")
|
||||
view_oidc |> element("a", "Profil") |> render_click()
|
||||
assert_redirected(view_oidc, "/users/#{oidc_user.id}")
|
||||
assert_redirected(view_oidc, "/users/#{oidc_user_with_role.id}")
|
||||
end
|
||||
end
|
||||
|
||||
describe "authenticated views" do
|
||||
# User must have a role with page permission to access /members, /users, etc.
|
||||
setup %{conn: conn} do
|
||||
user = create_test_user(%{email: "test@example.com"})
|
||||
user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||
conn = conn_with_password_user(conn, user)
|
||||
{:ok, conn: conn, user: user}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -441,18 +441,11 @@ defmodule MvWeb.RoleLiveTest do
|
|||
end
|
||||
|
||||
test "only admin can access /admin/roles", %{conn: conn, actor: actor} do
|
||||
{conn, _user} = create_non_admin_user(conn, actor)
|
||||
{conn, user} = create_non_admin_user(conn, actor)
|
||||
|
||||
# Non-admin should be redirected or see error
|
||||
# Note: Authorization is checked via can_access_page? which returns false
|
||||
# The page might still mount but show no content or redirect
|
||||
# For now, we just verify the page doesn't work as expected for non-admin
|
||||
{:ok, _view, html} = live(conn, "/admin/roles")
|
||||
|
||||
# Non-admin should not see "New Role" button (can? returns false)
|
||||
# But the button might still be in HTML, just hidden or disabled
|
||||
# We verify that the page loads but admin features are restricted
|
||||
assert html =~ "Listing Roles" || html =~ "Roles"
|
||||
# Non-admin (no role or non-admin role) is redirected by CheckPagePermission plug
|
||||
assert {:error, {:redirect, %{to: to}}} = live(conn, "/admin/roles")
|
||||
assert to == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
test "admin can access /admin/roles", %{conn: conn, actor: actor} do
|
||||
|
|
|
|||
934
test/mv_web/plugs/check_page_permission_test.exs
Normal file
934
test/mv_web/plugs/check_page_permission_test.exs
Normal file
|
|
@ -0,0 +1,934 @@
|
|||
defmodule MvWeb.Plugs.CheckPagePermissionTest do
|
||||
@moduledoc """
|
||||
Tests for the CheckPagePermission plug.
|
||||
"""
|
||||
use MvWeb.ConnCase, async: true
|
||||
|
||||
alias MvWeb.Plugs.CheckPagePermission
|
||||
alias Mv.Fixtures
|
||||
|
||||
defp conn_with_user(path, user) do
|
||||
build_conn(:get, path)
|
||||
|> Phoenix.ConnTest.init_test_session(%{})
|
||||
|> Plug.Conn.put_private(:phoenix_router, MvWeb.Router)
|
||||
|> Plug.Conn.assign(:current_user, user)
|
||||
end
|
||||
|
||||
defp conn_without_user(path) do
|
||||
build_conn(:get, path)
|
||||
|> Phoenix.ConnTest.init_test_session(%{})
|
||||
|> Plug.Conn.put_private(:phoenix_router, MvWeb.Router)
|
||||
end
|
||||
|
||||
describe "static routes" do
|
||||
test "user with permission for \"/members\" can access (conn not halted)" do
|
||||
user = Fixtures.user_with_role_fixture("read_only")
|
||||
conn = conn_with_user("/members", user) |> CheckPagePermission.call([])
|
||||
|
||||
refute conn.halted
|
||||
end
|
||||
|
||||
test "user without permission for \"/members\" is denied (conn halted, redirected to user profile)" do
|
||||
user = Fixtures.user_with_role_fixture("own_data")
|
||||
conn = conn_with_user("/members", user) |> CheckPagePermission.call([])
|
||||
|
||||
assert conn.halted
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
test "flash error message present after denial" do
|
||||
user = Fixtures.user_with_role_fixture("own_data")
|
||||
conn = conn_with_user("/members", user) |> CheckPagePermission.call([])
|
||||
|
||||
assert Phoenix.Flash.get(conn.assigns[:flash] || %{}, :error) ==
|
||||
"You don't have permission to access this page."
|
||||
end
|
||||
end
|
||||
|
||||
describe "dynamic routes" do
|
||||
test "user with \"/members/:id\" permission can access \"/members/123\"" do
|
||||
user = Fixtures.user_with_role_fixture("read_only")
|
||||
conn = conn_with_user("/members/123", user) |> CheckPagePermission.call([])
|
||||
|
||||
refute conn.halted
|
||||
end
|
||||
|
||||
test "user with \"/members/:id/edit\" permission can access \"/members/456/edit\"" do
|
||||
user = Fixtures.user_with_role_fixture("normal_user")
|
||||
conn = conn_with_user("/members/456/edit", user) |> CheckPagePermission.call([])
|
||||
|
||||
refute conn.halted
|
||||
end
|
||||
|
||||
test "user with only \"/members/:id\" cannot access \"/members/123/edit\"" do
|
||||
user = Fixtures.user_with_role_fixture("read_only")
|
||||
conn = conn_with_user("/members/123/edit", user) |> CheckPagePermission.call([])
|
||||
|
||||
assert conn.halted
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
test "own_data user with linked member can access /members/:id/edit (plug direct call)" do
|
||||
member = Fixtures.member_fixture()
|
||||
user = Fixtures.user_with_role_fixture("own_data")
|
||||
user_with_member = Mv.Authorization.Actor.ensure_loaded(user)
|
||||
# Simulate user with linked member (struct may not have member_id after session load)
|
||||
user_with_member = %{user_with_member | member_id: member.id}
|
||||
|
||||
assert CheckPagePermission.user_can_access_page?(
|
||||
user_with_member,
|
||||
"/members/#{member.id}/edit"
|
||||
),
|
||||
"plug must allow own_data user with linked member to access member edit"
|
||||
|
||||
conn =
|
||||
conn_with_user("/members/#{member.id}/edit", user_with_member)
|
||||
|> CheckPagePermission.call([])
|
||||
|
||||
refute conn.halted
|
||||
end
|
||||
|
||||
test "own_data user with linked member can access /members/:id/show/edit (plug direct call)" do
|
||||
member = Fixtures.member_fixture()
|
||||
user = Fixtures.user_with_role_fixture("own_data")
|
||||
user_with_member = Mv.Authorization.Actor.ensure_loaded(user)
|
||||
user_with_member = %{user_with_member | member_id: member.id}
|
||||
|
||||
assert CheckPagePermission.user_can_access_page?(
|
||||
user_with_member,
|
||||
"/members/#{member.id}/show/edit"
|
||||
)
|
||||
|
||||
conn =
|
||||
conn_with_user("/members/#{member.id}/show/edit", user_with_member)
|
||||
|> CheckPagePermission.call([])
|
||||
|
||||
refute conn.halted
|
||||
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")
|
||||
conn = conn_with_user("/admin/roles", user) |> CheckPagePermission.call([])
|
||||
|
||||
assert conn.halted
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
test "normal_user cannot access /admin/roles" do
|
||||
user = Fixtures.user_with_role_fixture("normal_user")
|
||||
conn = conn_with_user("/admin/roles", user) |> CheckPagePermission.call([])
|
||||
|
||||
assert conn.halted
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
test "read_only cannot access /members/new" do
|
||||
user = Fixtures.user_with_role_fixture("read_only")
|
||||
conn = conn_with_user("/members/new", user) |> CheckPagePermission.call([])
|
||||
|
||||
assert conn.halted
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "wildcard" do
|
||||
test "admin with \"*\" permission can access any page" do
|
||||
user = Fixtures.user_with_role_fixture("admin")
|
||||
conn = conn_with_user("/admin/roles", user) |> CheckPagePermission.call([])
|
||||
|
||||
refute conn.halted
|
||||
end
|
||||
|
||||
test "admin can access \"/members/999/edit\"" do
|
||||
user = Fixtures.user_with_role_fixture("admin")
|
||||
conn = conn_with_user("/members/999/edit", user) |> CheckPagePermission.call([])
|
||||
|
||||
refute conn.halted
|
||||
end
|
||||
end
|
||||
|
||||
describe "unauthenticated user" do
|
||||
test "nil current_user is denied and redirected to \"/sign-in\"" do
|
||||
conn = conn_without_user("/members") |> CheckPagePermission.call([])
|
||||
|
||||
assert conn.halted
|
||||
assert redirected_to(conn) == "/sign-in"
|
||||
|
||||
assert Phoenix.Flash.get(conn.assigns[:flash] || %{}, :error) ==
|
||||
"You don't have permission to access this page."
|
||||
end
|
||||
end
|
||||
|
||||
describe "public paths" do
|
||||
test "unauthenticated user can access /auth/sign-in (no redirect)" do
|
||||
conn = conn_without_user("/auth/sign-in") |> CheckPagePermission.call([])
|
||||
|
||||
refute conn.halted
|
||||
end
|
||||
|
||||
test "unauthenticated user can access /register" do
|
||||
conn = conn_without_user("/register") |> CheckPagePermission.call([])
|
||||
|
||||
refute conn.halted
|
||||
end
|
||||
end
|
||||
|
||||
describe "error handling" do
|
||||
test "user with no role is denied" do
|
||||
user = Fixtures.user_with_role_fixture("admin")
|
||||
user_without_role = %{user | role: nil}
|
||||
conn = conn_with_user("/members", user_without_role) |> CheckPagePermission.call([])
|
||||
|
||||
assert conn.halted
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
test "user with invalid permission_set_name is denied" do
|
||||
user = Fixtures.user_with_role_fixture("admin")
|
||||
bad_role = %{user.role | permission_set_name: "invalid_set"}
|
||||
user_bad_role = %{user | role: bad_role}
|
||||
conn = conn_with_user("/members", user_bad_role) |> CheckPagePermission.call([])
|
||||
|
||||
assert conn.halted
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
end
|
||||
|
||||
# Integration: dispatch through full router (endpoint) so pipeline and load_from_session run.
|
||||
# These tests ensure a Mitglied (own_data) user is denied on every forbidden path.
|
||||
describe "integration: Mitglied (own_data) denied on all forbidden paths via full router" do
|
||||
@tag role: :member
|
||||
test "GET /members redirects to user profile with error flash", %{
|
||||
conn: conn,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/members")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
|
||||
assert Phoenix.Flash.get(conn.assigns[:flash] || %{}, :error) =~
|
||||
"don't have permission"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /members/new redirects to user profile", %{conn: conn, current_user: user} do
|
||||
assert user.role.permission_set_name == "own_data",
|
||||
"setup must provide Mitglied (own_data) user"
|
||||
|
||||
conn = get(conn, "/members/new")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /users redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/users")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /users/new redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/users/new")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /settings redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/settings")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /membership_fee_settings redirects to user profile", %{
|
||||
conn: conn,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/membership_fee_settings")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /membership_fee_types redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/membership_fee_types")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /membership_fee_types/new redirects to user profile", %{
|
||||
conn: conn,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/membership_fee_types/new")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /groups redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/groups")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /groups/new redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/groups/new")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /admin/roles redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/admin/roles")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /admin/roles/new redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/admin/roles/new")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
end
|
||||
|
||||
# Dynamic routes need a valid path segment; use a real UUID from fixtures.
|
||||
describe "integration: Mitglied denied on dynamic forbidden paths via full router" do
|
||||
setup %{conn: conn, current_user: current_user} do
|
||||
member = Mv.Fixtures.member_fixture()
|
||||
role = Mv.Fixtures.role_fixture("admin")
|
||||
group = Mv.Fixtures.group_fixture()
|
||||
|
||||
{:ok,
|
||||
conn: conn,
|
||||
current_user: current_user,
|
||||
member_id: member.id,
|
||||
role_id: role.id,
|
||||
group_slug: group.slug}
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /members/:id/edit redirects to user profile", %{
|
||||
conn: conn,
|
||||
member_id: id,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/members/#{id}/edit")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /members/:id/show/edit redirects to user profile", %{
|
||||
conn: conn,
|
||||
member_id: id,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/members/#{id}/show/edit")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /members/:id (unlinked member show) redirects to user profile", %{
|
||||
conn: conn,
|
||||
member_id: id,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/members/#{id}")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /users/:id redirects to user profile", %{conn: conn, current_user: user} do
|
||||
other_user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||
conn = get(conn, "/users/#{other_user.id}")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /users/:id/edit redirects to user profile", %{conn: conn, current_user: user} do
|
||||
other_user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||
conn = get(conn, "/users/#{other_user.id}/edit")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /users/:id/show/edit redirects to user profile", %{conn: conn, current_user: user} do
|
||||
other_user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||
conn = get(conn, "/users/#{other_user.id}/show/edit")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /membership_fee_types/:id/edit redirects to user profile", %{
|
||||
conn: conn,
|
||||
current_user: user
|
||||
} do
|
||||
type =
|
||||
Mv.MembershipFees.MembershipFeeType
|
||||
|> Ash.Query.limit(1)
|
||||
|> Ash.read!(actor: Mv.Helpers.SystemActor.get_system_actor())
|
||||
|> List.first()
|
||||
|
||||
if type do
|
||||
conn = get(conn, "/membership_fee_types/#{type.id}/edit")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /groups/:slug redirects to user profile", %{
|
||||
conn: conn,
|
||||
current_user: user,
|
||||
group_slug: slug
|
||||
} do
|
||||
assert redirected_to(get(conn, "/groups/#{slug}")) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /admin/roles/:id redirects to user profile", %{
|
||||
conn: conn,
|
||||
role_id: id,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/admin/roles/#{id}")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /admin/roles/:id/edit redirects to user profile", %{
|
||||
conn: conn,
|
||||
role_id: id,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/admin/roles/#{id}/edit")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "integration: Mitglied (own_data) can access allowed paths via full router" do
|
||||
@tag role: :member
|
||||
test "GET / redirects to user profile (root not allowed for own_data)", %{
|
||||
conn: conn,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /users/:id (own profile) returns 200", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/users/#{user.id}")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /users/:id/edit (own profile edit) returns 200", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/users/#{user.id}/edit")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
test "GET /users/:id/show/edit (own profile show edit) returns 200", %{
|
||||
conn: conn,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/users/#{user.id}/show/edit")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
# Full-router test: session may not preserve member_id; plug logic covered by unit test "own_data user with linked member can access /members/:id/edit (plug direct call)"
|
||||
@tag role: :member
|
||||
@tag :skip
|
||||
test "GET /members/:id/edit (linked member edit) returns 200 when user has linked member", %{
|
||||
conn: conn,
|
||||
current_user: user
|
||||
} do
|
||||
member = Mv.Fixtures.member_fixture()
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
{:ok, user_after_update} =
|
||||
user
|
||||
|> Ash.Changeset.for_update(:update, %{})
|
||||
|> Ash.Changeset.force_set_argument(:member, %{id: member.id})
|
||||
|> Ash.update(actor: system_actor)
|
||||
|
||||
user_with_member =
|
||||
user_after_update
|
||||
|> Ash.load!([:role], domain: Mv.Accounts)
|
||||
|> Mv.Authorization.Actor.ensure_loaded()
|
||||
|> Map.put(:member_id, member.id)
|
||||
|
||||
conn = conn_with_password_user(conn, user_with_member)
|
||||
|
||||
conn = get(conn, "/members/#{member.id}/edit")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :member
|
||||
@tag :skip
|
||||
test "GET /members/:id/show/edit (linked member show edit) returns 200 when user has linked member",
|
||||
%{
|
||||
conn: conn,
|
||||
current_user: user
|
||||
} do
|
||||
member = Mv.Fixtures.member_fixture()
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
{:ok, user_after_update} =
|
||||
user
|
||||
|> Ash.Changeset.for_update(:update, %{})
|
||||
|> Ash.Changeset.force_set_argument(:member, %{id: member.id})
|
||||
|> Ash.update(actor: system_actor)
|
||||
|
||||
user_with_member =
|
||||
user_after_update
|
||||
|> Ash.load!([:role], domain: Mv.Accounts)
|
||||
|> Mv.Authorization.Actor.ensure_loaded()
|
||||
|> Map.put(:member_id, member.id)
|
||||
|
||||
conn = conn_with_password_user(conn, user_with_member)
|
||||
|
||||
conn = get(conn, "/members/#{member.id}/show/edit")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
# Skipped: MemberLive.Show requires membership fee cycle data; plug allows access (page loads then LiveView may error).
|
||||
@tag role: :member
|
||||
@tag :skip
|
||||
test "GET /members/:id for linked member returns 200", %{conn: conn, current_user: user} do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
member = Mv.Fixtures.member_fixture()
|
||||
|
||||
user =
|
||||
user
|
||||
|> Ash.Changeset.for_update(:update_user, %{})
|
||||
|> Ash.Changeset.force_set_argument(:member, %{id: member.id})
|
||||
|> Ash.update(actor: system_actor)
|
||||
|> case do
|
||||
{:ok, u} -> Ash.load!(u, :role, domain: Mv.Accounts, actor: system_actor)
|
||||
{:error, _} -> user
|
||||
end
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> MvWeb.ConnCase.conn_with_password_user(user)
|
||||
|> get("/members/#{member.id}")
|
||||
|
||||
assert conn.status == 200
|
||||
end
|
||||
end
|
||||
|
||||
# read_only (Vorstand/Buchhaltung): allowed /, /members, /members/:id, /groups, /groups/:slug
|
||||
describe "integration: read_only (Vorstand/Buchhaltung) allowed paths via full router" do
|
||||
setup %{conn: conn, current_user: current_user} do
|
||||
member = Mv.Fixtures.member_fixture()
|
||||
group = Mv.Fixtures.group_fixture()
|
||||
|
||||
{:ok, conn: conn, current_user: current_user, member_id: member.id, group_slug: group.slug}
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET / returns 200", %{conn: conn} do
|
||||
conn = get(conn, "/")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /members returns 200", %{conn: conn} do
|
||||
conn = get(conn, "/members")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /members/:id returns 200", %{conn: conn, member_id: id} do
|
||||
conn = get(conn, "/members/#{id}")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /groups returns 200", %{conn: conn} do
|
||||
conn = get(conn, "/groups")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /groups/:slug returns 200", %{conn: conn, group_slug: slug} do
|
||||
conn = get(conn, "/groups/#{slug}")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /users/:id (own profile) returns 200", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/users/#{user.id}")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /users/:id/edit (own profile edit) returns 200", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/users/#{user.id}/edit")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /users/:id/show/edit (own profile show edit) returns 200", %{
|
||||
conn: conn,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/users/#{user.id}/show/edit")
|
||||
assert conn.status == 200
|
||||
end
|
||||
end
|
||||
|
||||
describe "integration: read_only denied paths via full router" do
|
||||
setup %{conn: conn, current_user: current_user} do
|
||||
member = Mv.Fixtures.member_fixture()
|
||||
role = Mv.Fixtures.role_fixture("admin")
|
||||
group = Mv.Fixtures.group_fixture()
|
||||
|
||||
type =
|
||||
Mv.MembershipFees.MembershipFeeType
|
||||
|> Ash.Query.limit(1)
|
||||
|> Ash.read!(actor: Mv.Helpers.SystemActor.get_system_actor())
|
||||
|> List.first()
|
||||
|
||||
{:ok,
|
||||
conn: conn,
|
||||
current_user: current_user,
|
||||
member_id: member.id,
|
||||
role_id: role.id,
|
||||
group_slug: group.slug,
|
||||
fee_type_id: type && type.id}
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /members/new redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/members/new")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /members/:id/edit redirects to user profile", %{
|
||||
conn: conn,
|
||||
member_id: id,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/members/#{id}/edit")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /users redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/users")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /users/new redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/users/new")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /users/:id (other user) redirects to user profile", %{
|
||||
conn: conn,
|
||||
current_user: user,
|
||||
role_id: _role_id
|
||||
} do
|
||||
other_user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||
conn = get(conn, "/users/#{other_user.id}")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /settings redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/settings")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /membership_fee_settings redirects to user profile", %{
|
||||
conn: conn,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/membership_fee_settings")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /membership_fee_types redirects to user profile", %{
|
||||
conn: conn,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/membership_fee_types")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /groups/new redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/groups/new")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /groups/:slug/edit redirects to user profile", %{
|
||||
conn: conn,
|
||||
current_user: user,
|
||||
group_slug: slug
|
||||
} do
|
||||
conn = get(conn, "/groups/#{slug}/edit")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /admin/roles redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/admin/roles")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :read_only
|
||||
test "GET /admin/roles/:id redirects to user profile", %{
|
||||
conn: conn,
|
||||
role_id: id,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/admin/roles/#{id}")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
end
|
||||
|
||||
# normal_user (Kassenwart): allowed /, /members, /members/new, /members/:id, /members/:id/edit, /groups, /groups/:slug
|
||||
describe "integration: normal_user (Kassenwart) allowed paths via full router" do
|
||||
setup %{conn: conn, current_user: current_user} do
|
||||
member = Mv.Fixtures.member_fixture()
|
||||
group = Mv.Fixtures.group_fixture()
|
||||
|
||||
{:ok, conn: conn, current_user: current_user, member_id: member.id, group_slug: group.slug}
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET / returns 200", %{conn: conn} do
|
||||
conn = get(conn, "/")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /members returns 200", %{conn: conn} do
|
||||
conn = get(conn, "/members")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /members/new returns 200", %{conn: conn} do
|
||||
conn = get(conn, "/members/new")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /members/:id returns 200", %{conn: conn, member_id: id} do
|
||||
conn = get(conn, "/members/#{id}")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /members/:id/edit returns 200", %{conn: conn, member_id: id} do
|
||||
conn = get(conn, "/members/#{id}/edit")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /groups returns 200", %{conn: conn} do
|
||||
conn = get(conn, "/groups")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /groups/:slug returns 200", %{conn: conn, group_slug: slug} do
|
||||
conn = get(conn, "/groups/#{slug}")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /members/:id/show/edit returns 200", %{conn: conn, member_id: id} do
|
||||
conn = get(conn, "/members/#{id}/show/edit")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /users/:id (own profile) returns 200", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/users/#{user.id}")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /users/:id/edit (own profile edit) returns 200", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/users/#{user.id}/edit")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /users/:id/show/edit (own profile show edit) returns 200", %{
|
||||
conn: conn,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/users/#{user.id}/show/edit")
|
||||
assert conn.status == 200
|
||||
end
|
||||
end
|
||||
|
||||
describe "integration: normal_user denied paths via full router" do
|
||||
setup %{conn: conn, current_user: current_user} do
|
||||
other_user = Mv.Fixtures.user_with_role_fixture("admin")
|
||||
role = Mv.Fixtures.role_fixture("admin")
|
||||
group = Mv.Fixtures.group_fixture()
|
||||
|
||||
{:ok,
|
||||
conn: conn,
|
||||
current_user: current_user,
|
||||
other_user_id: other_user.id,
|
||||
role_id: role.id,
|
||||
group_slug: group.slug}
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /users redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/users")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /users/new redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/users/new")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /users/:id redirects to user profile", %{
|
||||
conn: conn,
|
||||
current_user: user,
|
||||
other_user_id: id
|
||||
} do
|
||||
conn = get(conn, "/users/#{id}")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /settings redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/settings")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /membership_fee_settings redirects to user profile", %{
|
||||
conn: conn,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/membership_fee_settings")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /membership_fee_types redirects to user profile", %{
|
||||
conn: conn,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/membership_fee_types")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /groups/new redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/groups/new")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /groups/:slug/edit redirects to user profile", %{
|
||||
conn: conn,
|
||||
current_user: user,
|
||||
group_slug: slug
|
||||
} do
|
||||
conn = get(conn, "/groups/#{slug}/edit")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /admin/roles redirects to user profile", %{conn: conn, current_user: user} do
|
||||
conn = get(conn, "/admin/roles")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
|
||||
@tag role: :normal_user
|
||||
test "GET /admin/roles/:id redirects to user profile", %{
|
||||
conn: conn,
|
||||
role_id: id,
|
||||
current_user: user
|
||||
} do
|
||||
conn = get(conn, "/admin/roles/#{id}")
|
||||
assert redirected_to(conn) == "/users/#{user.id}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "integration: admin can access all protected routes via full router" do
|
||||
setup %{conn: conn, current_user: current_user} do
|
||||
member = Mv.Fixtures.member_fixture()
|
||||
role = Mv.Fixtures.role_fixture("admin")
|
||||
group = Mv.Fixtures.group_fixture()
|
||||
|
||||
{:ok,
|
||||
conn: conn,
|
||||
current_user: current_user,
|
||||
member_id: member.id,
|
||||
role_id: role.id,
|
||||
group_slug: group.slug}
|
||||
end
|
||||
|
||||
@tag role: :admin
|
||||
test "GET / returns 200", %{conn: conn} do
|
||||
conn = get(conn, "/")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :admin
|
||||
test "GET /members returns 200", %{conn: conn} do
|
||||
conn = get(conn, "/members")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :admin
|
||||
test "GET /users returns 200", %{conn: conn} do
|
||||
conn = get(conn, "/users")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :admin
|
||||
test "GET /settings returns 200", %{conn: conn} do
|
||||
conn = get(conn, "/settings")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :admin
|
||||
test "GET /membership_fee_settings returns 200", %{conn: conn} do
|
||||
conn = get(conn, "/membership_fee_settings")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :admin
|
||||
test "GET /admin/roles returns 200", %{conn: conn} do
|
||||
conn = get(conn, "/admin/roles")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :admin
|
||||
test "GET /members/:id returns 200", %{conn: conn, member_id: id} do
|
||||
conn = get(conn, "/members/#{id}")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :admin
|
||||
test "GET /admin/roles/:id returns 200", %{conn: conn, role_id: id} do
|
||||
conn = get(conn, "/admin/roles/#{id}")
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
@tag role: :admin
|
||||
test "GET /groups/:slug returns 200", %{conn: conn, group_slug: slug} do
|
||||
conn = get(conn, "/groups/#{slug}")
|
||||
assert conn.status == 200
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -175,6 +175,18 @@ defmodule MvWeb.ConnCase do
|
|||
authenticated_conn = conn_with_password_user(conn, member_user)
|
||||
{authenticated_conn, member_user}
|
||||
|
||||
:read_only ->
|
||||
# Vorstand/Buchhaltung: can read members, groups; cannot edit or access admin/settings
|
||||
read_only_user = Mv.Fixtures.user_with_role_fixture("read_only")
|
||||
authenticated_conn = conn_with_password_user(conn, read_only_user)
|
||||
{authenticated_conn, read_only_user}
|
||||
|
||||
:normal_user ->
|
||||
# Kassenwart: can read/update members, groups; cannot access users/settings/admin
|
||||
normal_user = Mv.Fixtures.user_with_role_fixture("normal_user")
|
||||
authenticated_conn = conn_with_password_user(conn, normal_user)
|
||||
{authenticated_conn, normal_user}
|
||||
|
||||
:unauthenticated ->
|
||||
# No authentication for unauthenticated tests
|
||||
{conn, nil}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue