refactor: improve seeds tests performance by reducing complexity
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Simon 2026-01-28 11:31:31 +01:00
parent 1f8fa8a6fb
commit f9403c1da9
Signed by: simon
GPG key ID: 40E7A58C4AA1EDB2
4 changed files with 525 additions and 209 deletions

View file

@ -1,44 +1,74 @@
defmodule Mv.SeedsTest do
@moduledoc """
Minimal test suite for database seeds.
This test suite focuses on critical deployment requirements:
- Seeds run without errors (smoke test)
- Seeds are idempotent (can be run multiple times)
- Critical bootstrap invariants are met (Admin user, system roles)
## Removed Tests
The following tests were removed to reduce test execution time.
Their functionality is covered by domain-specific tests:
- Member/fee type distribution tests covered by `membership_fees/*_test.exs`
- Cycle status variation tests covered by `membership_fees/cycle_generator_test.exs`
- Detailed role configuration tests covered by `authorization/*_test.exs`
- Permission set validation covered by `authorization/permission_sets_test.exs`
See `docs/test-optimization-seeds.md` for detailed rationale and coverage mapping.
"""
use Mv.DataCase, async: false
require Ash.Query
# Module attribute to track if seeds have been run
# This allows us to run seeds once per test module while respecting sandbox isolation
@seeds_run_key :seeds_test_run
setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
# Run seeds only once per test run (check process dictionary)
unless Process.get(@seeds_run_key) do
Code.eval_file("priv/repo/seeds.exs")
Process.put(@seeds_run_key, true)
end
%{actor: system_actor}
end
describe "Seeds script" do
test "runs successfully without errors", %{actor: actor} do
# Run the seeds script - should not raise any errors
assert Code.eval_file("priv/repo/seeds.exs")
# Basic smoke test: ensure some data was created
test "runs successfully and creates basic data", %{actor: actor} do
# Smoke test: verify seeds created essential data structures
{:ok, users} = Ash.read(Mv.Accounts.User, actor: actor)
{:ok, members} = Ash.read(Mv.Membership.Member, actor: actor)
{:ok, custom_fields} = Ash.read(Mv.Membership.CustomField, actor: actor)
{:ok, roles} = Ash.read(Mv.Authorization.Role, domain: Mv.Authorization, authorize?: false)
assert not Enum.empty?(users), "Seeds should create at least one user"
assert not Enum.empty?(members), "Seeds should create at least one member"
assert not Enum.empty?(custom_fields), "Seeds should create at least one custom field"
assert length(roles) >= 5, "Seeds should create at least 5 authorization roles"
end
test "can be run multiple times (idempotent)", %{actor: actor} do
# Run seeds first time
assert Code.eval_file("priv/repo/seeds.exs")
# Count records
test "is idempotent when run multiple times", %{actor: actor} do
# Seeds already run once in setup_all - count current state
{:ok, users_count_1} = Ash.read(Mv.Accounts.User, actor: actor)
{:ok, members_count_1} = Ash.read(Mv.Membership.Member, actor: actor)
{:ok, custom_fields_count_1} = Ash.read(Mv.Membership.CustomField, actor: actor)
{:ok, roles_count_1} =
Ash.read(Mv.Authorization.Role, domain: Mv.Authorization, authorize?: false)
# Run seeds second time - should not raise errors
# Run seeds again - should not raise errors and should be idempotent
assert Code.eval_file("priv/repo/seeds.exs")
# Count records again - should be the same (upsert, not duplicate)
{:ok, users_count_2} = Ash.read(Mv.Accounts.User, actor: actor)
{:ok, members_count_2} = Ash.read(Mv.Membership.Member, actor: actor)
{:ok, custom_fields_count_2} = Ash.read(Mv.Membership.CustomField, actor: actor)
{:ok, roles_count_2} =
Ash.read(Mv.Authorization.Role, domain: Mv.Authorization, authorize?: false)
assert length(users_count_1) == length(users_count_2),
"Users count should remain same after re-running seeds"
@ -46,215 +76,45 @@ defmodule Mv.SeedsTest do
assert length(members_count_1) == length(members_count_2),
"Members count should remain same after re-running seeds"
assert length(custom_fields_count_1) == length(custom_fields_count_2),
"CustomFields count should remain same after re-running seeds"
end
test "at least one member has no membership fee type assigned", %{actor: actor} do
# Run the seeds script
assert Code.eval_file("priv/repo/seeds.exs")
# Get all members
{:ok, members} = Ash.read(Mv.Membership.Member, actor: actor)
# At least one member should have no membership_fee_type_id
members_without_fee_type =
Enum.filter(members, fn member -> member.membership_fee_type_id == nil end)
assert not Enum.empty?(members_without_fee_type),
"At least one member should have no membership fee type assigned"
end
test "each membership fee type has at least one member", %{actor: actor} do
# Run the seeds script
assert Code.eval_file("priv/repo/seeds.exs")
# Get all fee types and members
{:ok, fee_types} = Ash.read(Mv.MembershipFees.MembershipFeeType, actor: actor)
{:ok, members} = Ash.read(Mv.Membership.Member, actor: actor)
# Group members by fee type (excluding nil)
members_by_fee_type =
members
|> Enum.filter(&(&1.membership_fee_type_id != nil))
|> Enum.group_by(& &1.membership_fee_type_id)
# Each fee type should have at least one member
Enum.each(fee_types, fn fee_type ->
members_for_type = Map.get(members_by_fee_type, fee_type.id, [])
assert not Enum.empty?(members_for_type),
"Membership fee type #{fee_type.name} should have at least one member assigned"
end)
end
test "members with fee types have cycles with various statuses", %{actor: actor} do
# Run the seeds script
assert Code.eval_file("priv/repo/seeds.exs")
# Get all members with fee types
{:ok, members} = Ash.read(Mv.Membership.Member, actor: actor)
members_with_fee_types =
members
|> Enum.filter(&(&1.membership_fee_type_id != nil))
# At least one member should have cycles
assert not Enum.empty?(members_with_fee_types),
"At least one member should have a membership fee type"
# Check that cycles exist and have various statuses
all_cycle_statuses =
members_with_fee_types
|> Enum.flat_map(fn member ->
Mv.MembershipFees.MembershipFeeCycle
|> Ash.Query.filter(member_id == ^member.id)
|> Ash.read!(actor: actor)
end)
|> Enum.map(& &1.status)
# At least one cycle should be paid
assert :paid in all_cycle_statuses, "At least one cycle should be paid"
# At least one cycle should be unpaid
assert :unpaid in all_cycle_statuses, "At least one cycle should be unpaid"
# At least one cycle should be suspended
assert :suspended in all_cycle_statuses, "At least one cycle should be suspended"
assert length(roles_count_1) == length(roles_count_2),
"Roles count should remain same after re-running seeds"
end
end
describe "Authorization roles (from seeds)" do
test "creates all 5 authorization roles with correct permission sets" do
# Run seeds once for this test
Code.eval_file("priv/repo/seeds.exs")
{:ok, roles} = Ash.read(Mv.Authorization.Role, domain: Mv.Authorization, authorize?: false)
describe "Critical bootstrap invariants" do
test "Admin user has Admin role and can authenticate", %{actor: _actor} do
# Critical: Without this, initial system setup fails
admin_email = System.get_env("ADMIN_EMAIL") || "admin@localhost"
assert length(roles) >= 5, "Should have at least 5 roles"
{:ok, admin_user} =
Mv.Accounts.User
|> Ash.Query.filter(email == ^admin_email)
|> Ash.read_one(domain: Mv.Accounts, authorize?: false)
# Check each role
role_configs = [
{"Mitglied", "own_data", true},
{"Vorstand", "read_only", false},
{"Kassenwart", "normal_user", false},
{"Buchhaltung", "read_only", false},
{"Admin", "admin", false}
]
assert admin_user != nil, "Admin user must exist after seeds run"
Enum.each(role_configs, fn {name, perm_set, is_system} ->
role = Enum.find(roles, &(&1.name == name))
assert role, "Role #{name} should exist"
assert role.permission_set_name == perm_set
assert role.is_system_role == is_system
end)
{:ok, admin_user_with_role} =
Ash.load(admin_user, :role, domain: Mv.Accounts, authorize?: false)
assert admin_user_with_role.role != nil, "Admin user must have a role assigned"
assert admin_user_with_role.role.name == "Admin", "Admin user must have Admin role"
assert admin_user_with_role.role.permission_set_name == "admin",
"Admin role must have admin permission set"
end
test "Mitglied role is marked as system role" do
Code.eval_file("priv/repo/seeds.exs")
test "Mitglied system role exists and is correctly configured" do
# Critical: Default role for new users - system breaks without this
{:ok, mitglied} =
Mv.Authorization.Role
|> Ash.Query.filter(name == "Mitglied")
|> Ash.read_one(domain: Mv.Authorization, authorize?: false)
assert mitglied.is_system_role == true
end
assert mitglied != nil, "Mitglied role must exist"
assert mitglied.is_system_role == true, "Mitglied role must be marked as system role"
test "all roles have valid permission_set_names" do
Code.eval_file("priv/repo/seeds.exs")
{:ok, roles} = Ash.read(Mv.Authorization.Role, domain: Mv.Authorization, authorize?: false)
valid_sets =
Mv.Authorization.PermissionSets.all_permission_sets()
|> Enum.map(&Atom.to_string/1)
Enum.each(roles, fn role ->
assert role.permission_set_name in valid_sets,
"Role #{role.name} has invalid permission_set_name: #{role.permission_set_name}"
end)
end
test "assigns Admin role to ADMIN_EMAIL user" do
Code.eval_file("priv/repo/seeds.exs")
admin_email = System.get_env("ADMIN_EMAIL") || "admin@localhost"
{:ok, admin_user} =
Mv.Accounts.User
|> Ash.Query.filter(email == ^admin_email)
|> Ash.read_one(domain: Mv.Accounts, authorize?: false)
assert admin_user != nil, "Admin user should exist after seeds run"
{:ok, admin_user_with_role} =
Ash.load(admin_user, :role, domain: Mv.Accounts, authorize?: false)
assert admin_user_with_role.role != nil, "Admin user should have a role assigned"
assert admin_user_with_role.role.name == "Admin"
assert admin_user_with_role.role.permission_set_name == "admin"
end
end
describe "Authorization role assignment" do
test "does not change role of users who already have a role" do
# Seeds once (creates Admin with Admin role)
Code.eval_file("priv/repo/seeds.exs")
admin_email = System.get_env("ADMIN_EMAIL") || "admin@localhost"
{:ok, admin_user} =
Mv.Accounts.User
|> Ash.Query.filter(email == ^admin_email)
|> Ash.read_one(domain: Mv.Accounts, authorize?: false)
assert admin_user != nil, "Admin user should exist after seeds run"
{:ok, admin_user_with_role} =
Ash.load(admin_user, :role, domain: Mv.Accounts, authorize?: false)
assert admin_user_with_role.role != nil, "Admin user should have a role assigned"
original_role_id = admin_user_with_role.role_id
assert admin_user_with_role.role.name == "Admin"
# Seeds again
Code.eval_file("priv/repo/seeds.exs")
# Admin reloaded
{:ok, admin_reloaded} =
Mv.Accounts.User
|> Ash.Query.filter(email == ^admin_email)
|> Ash.read_one(domain: Mv.Accounts, authorize?: false)
assert admin_reloaded != nil, "Admin user should still exist after re-running seeds"
{:ok, admin_reloaded_with_role} =
Ash.load(admin_reloaded, :role, domain: Mv.Accounts, authorize?: false)
assert admin_reloaded_with_role.role != nil,
"Admin user should still have a role after re-running seeds"
assert admin_reloaded_with_role.role_id == original_role_id
assert admin_reloaded_with_role.role.name == "Admin"
end
test "role creation is idempotent" do
Code.eval_file("priv/repo/seeds.exs")
{:ok, roles_1} =
Ash.read(Mv.Authorization.Role, domain: Mv.Authorization, authorize?: false)
Code.eval_file("priv/repo/seeds.exs")
{:ok, roles_2} =
Ash.read(Mv.Authorization.Role, domain: Mv.Authorization, authorize?: false)
assert length(roles_1) == length(roles_2),
"Role count should remain same after re-running seeds"
# Each role should appear exactly once
role_names = Enum.map(roles_2, & &1.name)
assert length(role_names) == length(Enum.uniq(role_names)),
"Each role name should appear exactly once"
assert mitglied.permission_set_name == "own_data",
"Mitglied role must have own_data permission set"
end
end
end

View file

@ -1,2 +1,4 @@
ExUnit.start()
ExUnit.start(
slowest: 10 # shows 10 slowest tests at the end of the test run
)
Ecto.Adapters.SQL.Sandbox.mode(Mv.Repo, :manual)