Complete Permissions for Groups, Membership Fees, and User Role Assignment closes #404 #405

Merged
moritz merged 26 commits from feature/404_permission_completeness into main 2026-02-04 11:47:19 +01:00
2 changed files with 18 additions and 31 deletions
Showing only changes of commit 03d3a7eb1b - Show all commits

View file

@ -81,8 +81,8 @@ lib/
├── membership/ # Membership domain
│ ├── membership.ex # Domain definition
│ ├── member.ex # Member resource
│ ├── custom_field.ex # Custom field (definition) resource
│ ├── custom_field_value.ex # Custom field value resource
│ ├── custom_field.ex # CustomFieldValue type resource
│ ├── setting.ex # Global settings (singleton resource)
│ └── email.ex # Email custom type
├── membership_fees/ # MembershipFees domain
@ -194,7 +194,8 @@ test/
├── seeds_test.exs # Database seed tests
└── support/ # Test helpers
├── conn_case.ex # Controller test helpers
└── data_case.ex # Data layer test helpers
├── data_case.ex # Data layer test helpers
└── fixtures.ex # Shared test fixtures (Mv.Fixtures)
```
### 1.2 Module Organization
@ -1247,7 +1248,8 @@ test/
│ └── components/
└── support/ # Test helpers
├── conn_case.ex # Controller test setup
└── data_case.ex # Database test setup
├── data_case.ex # Database test setup
└── fixtures.ex # Shared test fixtures (Mv.Fixtures)
```
**Test File Naming:**

View file

@ -28,21 +28,6 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
|> Ash.create!(actor: system_actor)
end
# Helper to create a member
defp create_member(attrs) do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
default_attrs = %{
first_name: "Test",
last_name: "Member",
email: "test.member.#{System.unique_integer([:positive])}@example.com"
}
attrs = Map.merge(default_attrs, attrs)
{:ok, member} = Mv.Membership.create_member(attrs, actor: system_actor)
member
end
# Helper to create a cycle
defp create_cycle(member, fee_type, attrs) do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
@ -73,7 +58,7 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
describe "cycles table display" do
test "displays all cycles for member", %{conn: conn} do
fee_type = create_fee_type(%{interval: :yearly})
member = create_member(%{membership_fee_type_id: fee_type.id})
member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id})
_cycle1 = create_cycle(member, fee_type, %{cycle_start: ~D[2022-01-01], status: :paid})
_cycle2 = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid})
@ -95,7 +80,7 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
test "table columns show correct data", %{conn: conn} do
fee_type = create_fee_type(%{interval: :yearly, amount: Decimal.new("60.00")})
member = create_member(%{membership_fee_type_id: fee_type.id})
member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id})
create_cycle(member, fee_type, %{
cycle_start: ~D[2023-01-01],
@ -124,7 +109,7 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
yearly_type = create_fee_type(%{interval: :yearly, name: "Yearly Type"})
_monthly_type = create_fee_type(%{interval: :monthly, name: "Monthly Type"})
member = create_member(%{membership_fee_type_id: yearly_type.id})
member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: yearly_type.id})
{:ok, _view, html} = live(conn, "/members/#{member.id}")
@ -133,7 +118,7 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
end
test "shows no type message when no type assigned", %{conn: conn} do
member = create_member(%{})
member = Mv.Fixtures.member_fixture(%{})
{:ok, _view, html} = live(conn, "/members/#{member.id}")
@ -145,7 +130,7 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
describe "status change actions" do
test "mark as paid works", %{conn: conn} do
fee_type = create_fee_type(%{interval: :yearly})
member = create_member(%{membership_fee_type_id: fee_type.id})
member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id})
cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid})
@ -176,7 +161,7 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
test "mark as suspended works", %{conn: conn} do
fee_type = create_fee_type(%{interval: :yearly})
member = create_member(%{membership_fee_type_id: fee_type.id})
member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id})
cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid})
@ -207,7 +192,7 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
test "mark as unpaid works", %{conn: conn} do
fee_type = create_fee_type(%{interval: :yearly})
member = create_member(%{membership_fee_type_id: fee_type.id})
member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id})
cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :paid})
@ -240,7 +225,7 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
describe "cycle regeneration" do
test "manual regeneration button exists and can be clicked", %{conn: conn} do
fee_type = create_fee_type(%{interval: :yearly})
member = create_member(%{membership_fee_type_id: fee_type.id})
member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id})
{:ok, view, _html} = live(conn, "/members/#{member.id}")
@ -266,7 +251,7 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
describe "edge cases" do
test "handles members without membership fee type gracefully", %{conn: conn} do
# No fee type
member = create_member(%{})
member = Mv.Fixtures.member_fixture(%{})
{:ok, _view, html} = live(conn, "/members/#{member.id}")
@ -282,7 +267,7 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
conn: conn
} do
fee_type = create_fee_type(%{interval: :yearly})
member = create_member(%{membership_fee_type_id: fee_type.id})
member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id})
_cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid})
{:ok, view, _html} = live(conn, "/members/#{member.id}")
@ -301,7 +286,7 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
conn: conn
} do
fee_type = create_fee_type(%{interval: :yearly})
member = create_member(%{membership_fee_type_id: fee_type.id})
member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id})
cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid})
{:ok, view, _html} = live(conn, "/members/#{member.id}")
@ -327,7 +312,7 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
# (e.g. via dev tools), the server would enforce policy and show an error.
# This test verifies that Ash.destroy(cycle, actor: read_only_user) returns Forbidden.
fee_type = create_fee_type(%{interval: :yearly})
member = create_member(%{membership_fee_type_id: fee_type.id})
member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id})
cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid})
assert {:error, %Ash.Error.Forbidden{}} =
@ -342,7 +327,7 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
conn = put_session(conn, :locale, "en")
fee_type = create_fee_type(%{interval: :yearly})
member = create_member(%{membership_fee_type_id: fee_type.id})
member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id})
_c1 = create_cycle(member, fee_type, %{cycle_start: ~D[2022-01-01], status: :paid})
_c2 = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid})