141 lines
5.2 KiB
Elixir
141 lines
5.2 KiB
Elixir
defmodule Mv.Membership.GroupDatabaseConstraintsTest do
|
|
@moduledoc """
|
|
Tests for database-level constraints (unique, foreign keys, CASCADE).
|
|
These tests verify that constraints are enforced at the database level, not just application level.
|
|
"""
|
|
use Mv.DataCase, async: false
|
|
|
|
alias Mv.Membership
|
|
|
|
require Ash.Query
|
|
import Ash.Expr
|
|
|
|
setup do
|
|
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
|
%{actor: system_actor}
|
|
end
|
|
|
|
describe "Unique Constraints" do
|
|
test "database enforces unique name constraint (case-insensitive via LOWER)", %{actor: actor} do
|
|
{:ok, _group1} = Membership.create_group(%{name: "Test Group"}, actor: actor)
|
|
|
|
# Try to create with same name, different case - should fail at DB level
|
|
assert {:error, %Ash.Error.Invalid{errors: errors}} =
|
|
Membership.create_group(%{name: "TEST GROUP"}, actor: actor)
|
|
|
|
assert Enum.any?(errors, fn err ->
|
|
err.field == :name and
|
|
(String.contains?(err.message, "already been taken") or
|
|
String.contains?(err.message, "already exists") or
|
|
String.contains?(err.message, "duplicate"))
|
|
end)
|
|
end
|
|
|
|
test "database enforces unique slug constraint (case-sensitive)", %{actor: actor} do
|
|
{:ok, _group1} = Membership.create_group(%{name: "Test Group"}, actor: actor)
|
|
|
|
# Try to create with name that generates same slug - should fail at DB level
|
|
assert {:error, %Ash.Error.Invalid{errors: errors}} =
|
|
Membership.create_group(%{name: "test-group"}, actor: actor)
|
|
|
|
assert Enum.any?(errors, fn err ->
|
|
(err.field == :slug or err.field == :name) and
|
|
(String.contains?(err.message, "already been taken") or
|
|
String.contains?(err.message, "already exists") or
|
|
String.contains?(err.message, "duplicate"))
|
|
end)
|
|
end
|
|
end
|
|
|
|
describe "Foreign Key Constraints" do
|
|
test "cannot create MemberGroup with non-existent member_id", %{actor: actor} do
|
|
{:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor)
|
|
fake_member_id = Ash.UUID.generate()
|
|
|
|
assert {:error, %Ash.Error.Invalid{errors: errors}} =
|
|
Membership.create_member_group(%{member_id: fake_member_id, group_id: group.id},
|
|
actor: actor
|
|
)
|
|
|
|
assert Enum.any?(errors, fn err ->
|
|
(err.field == :member_id or err.field == :member) and
|
|
(String.contains?(err.message, "does not exist") or
|
|
String.contains?(err.message, "not found") or
|
|
String.contains?(err.message, "foreign key"))
|
|
end)
|
|
end
|
|
|
|
test "cannot create MemberGroup with non-existent group_id", %{actor: actor} do
|
|
{:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
|
|
fake_group_id = Ash.UUID.generate()
|
|
|
|
assert {:error, %Ash.Error.Invalid{errors: errors}} =
|
|
Membership.create_member_group(%{member_id: member.id, group_id: fake_group_id},
|
|
actor: actor
|
|
)
|
|
|
|
assert Enum.any?(errors, fn err ->
|
|
(err.field == :group_id or err.field == :group) and
|
|
(String.contains?(err.message, "does not exist") or
|
|
String.contains?(err.message, "not found") or
|
|
String.contains?(err.message, "foreign key"))
|
|
end)
|
|
end
|
|
end
|
|
|
|
describe "CASCADE Delete Constraints" do
|
|
test "deleting member cascades to member_groups (verified at DB level)", %{actor: actor} do
|
|
{:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
|
|
{:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor)
|
|
|
|
{:ok, member_group} =
|
|
Membership.create_member_group(%{member_id: member.id, group_id: group.id},
|
|
actor: actor
|
|
)
|
|
|
|
# Verify association exists
|
|
assert member_group.member_id == member.id
|
|
|
|
# Delete member
|
|
:ok = Membership.destroy_member(member, actor: actor)
|
|
|
|
# Verify MemberGroup is deleted at DB level (CASCADE)
|
|
{:ok, mgs} =
|
|
Ash.read(
|
|
Mv.Membership.MemberGroup
|
|
|> Ash.Query.filter(expr(id == ^member_group.id)),
|
|
actor: actor,
|
|
domain: Mv.Membership
|
|
)
|
|
|
|
assert mgs == []
|
|
end
|
|
|
|
test "deleting group cascades to member_groups (verified at DB level)", %{actor: actor} do
|
|
{:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
|
|
{:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor)
|
|
|
|
{:ok, member_group} =
|
|
Membership.create_member_group(%{member_id: member.id, group_id: group.id},
|
|
actor: actor
|
|
)
|
|
|
|
# Verify association exists
|
|
assert member_group.group_id == group.id
|
|
|
|
# Delete group
|
|
:ok = Membership.destroy_group(group, actor: actor)
|
|
|
|
# Verify MemberGroup is deleted at DB level (CASCADE)
|
|
{:ok, mgs} =
|
|
Ash.read(
|
|
Mv.Membership.MemberGroup
|
|
|> Ash.Query.filter(expr(id == ^member_group.id)),
|
|
actor: actor,
|
|
domain: Mv.Membership
|
|
)
|
|
|
|
assert mgs == []
|
|
end
|
|
end
|
|
end
|