2020-10-29 15:26:45 +13:00
|
|
|
defmodule AshPostgres.Test.MultitenancyTest do
|
|
|
|
use AshPostgres.RepoCase, async: false
|
|
|
|
|
2024-03-28 09:52:28 +13:00
|
|
|
alias AshPostgres.MultitenancyTest.{Org, Post, User}
|
2020-10-29 15:26:45 +13:00
|
|
|
|
|
|
|
setup do
|
|
|
|
org1 =
|
|
|
|
Org
|
2024-07-02 13:34:23 +12:00
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "test1"}, authorize?: false)
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2020-10-29 15:26:45 +13:00
|
|
|
|
|
|
|
org2 =
|
|
|
|
Org
|
2024-07-02 13:34:23 +12:00
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "test2"}, authorize?: false)
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2020-10-29 15:26:45 +13:00
|
|
|
|
|
|
|
[org1: org1, org2: org2]
|
|
|
|
end
|
|
|
|
|
|
|
|
defp tenant(org) do
|
|
|
|
"org_#{org.id}"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "listing tenants", %{org1: org1, org2: org2} do
|
|
|
|
tenant_ids =
|
|
|
|
[org1, org2]
|
|
|
|
|> Enum.map(&tenant/1)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert Enum.sort(AshPostgres.TestRepo.all_tenants()) == tenant_ids
|
|
|
|
end
|
|
|
|
|
|
|
|
test "attribute multitenancy works", %{org1: %{id: org_id} = org1} do
|
|
|
|
assert [%{id: ^org_id}] =
|
|
|
|
Org
|
|
|
|
|> Ash.Query.set_tenant(tenant(org1))
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.read!()
|
2020-10-29 15:26:45 +13:00
|
|
|
end
|
|
|
|
|
2024-07-02 13:34:23 +12:00
|
|
|
test "attribute multitenancy works with authorization", %{org1: org1} do
|
|
|
|
user =
|
|
|
|
User
|
|
|
|
|> Ash.Changeset.new()
|
|
|
|
|> Ash.Changeset.manage_relationship(:org, org1, type: :append_and_remove)
|
|
|
|
|> Ash.create!()
|
|
|
|
|
|
|
|
assert [] =
|
|
|
|
Org
|
|
|
|
|> Ash.Query.set_tenant(tenant(org1))
|
|
|
|
|> Ash.Query.for_read(:has_policies, %{}, actor: user, authorize?: true)
|
|
|
|
|> Ash.read!()
|
|
|
|
end
|
|
|
|
|
2023-12-23 15:14:40 +13:00
|
|
|
test "context multitenancy works with policies", %{org1: org1} do
|
2024-05-03 16:02:04 +12:00
|
|
|
post =
|
|
|
|
Post
|
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "foo"}, tenant: tenant(org1))
|
|
|
|
|> Ash.create!()
|
|
|
|
|
|
|
|
post
|
|
|
|
|> Ash.Changeset.for_update(:update_with_policy, %{}, authorize?: true, tenant: tenant(org1))
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.update!()
|
2023-12-23 15:14:40 +13:00
|
|
|
end
|
|
|
|
|
2020-10-29 15:26:45 +13:00
|
|
|
test "attribute multitenancy is set on creation" do
|
2021-02-25 07:59:49 +13:00
|
|
|
uuid = Ash.UUID.generate()
|
2020-10-29 15:26:45 +13:00
|
|
|
|
|
|
|
org =
|
|
|
|
Org
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "test3"})
|
2020-10-29 15:26:45 +13:00
|
|
|
|> Ash.Changeset.set_tenant("org_#{uuid}")
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2020-10-29 15:26:45 +13:00
|
|
|
|
|
|
|
assert org.id == uuid
|
|
|
|
end
|
|
|
|
|
|
|
|
test "schema multitenancy works", %{org1: org1, org2: org2} do
|
|
|
|
Post
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "foo"})
|
2020-10-29 15:26:45 +13:00
|
|
|
|> Ash.Changeset.set_tenant(tenant(org1))
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2020-10-29 15:26:45 +13:00
|
|
|
|
2024-03-28 09:52:28 +13:00
|
|
|
assert [_] = Post |> Ash.Query.set_tenant(tenant(org1)) |> Ash.read!()
|
|
|
|
assert [] = Post |> Ash.Query.set_tenant(tenant(org2)) |> Ash.read!()
|
2020-10-29 15:26:45 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "schema rename on update works", %{org1: org1} do
|
2021-02-25 07:59:49 +13:00
|
|
|
new_uuid = Ash.UUID.generate()
|
2020-10-29 15:26:45 +13:00
|
|
|
|
|
|
|
org1
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.Changeset.for_update(:update, %{id: new_uuid})
|
|
|
|
|> Ash.update!()
|
2020-10-29 15:26:45 +13:00
|
|
|
|
|
|
|
new_tenant = "org_#{new_uuid}"
|
|
|
|
|
|
|
|
assert {:ok, %{rows: [[^new_tenant]]}} =
|
|
|
|
Ecto.Adapters.SQL.query(
|
|
|
|
AshPostgres.TestRepo,
|
|
|
|
"""
|
2021-09-14 04:58:23 +12:00
|
|
|
SELECT schema_name FROM information_schema.schemata WHERE schema_name = '#{new_tenant}';
|
2020-10-29 15:26:45 +13:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
end
|
2021-01-27 09:07:26 +13:00
|
|
|
|
2021-07-25 03:28:58 +12:00
|
|
|
test "loading attribute multitenant resources from context multitenant resources works" do
|
|
|
|
org =
|
|
|
|
Org
|
|
|
|
|> Ash.Changeset.new()
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2021-07-25 03:28:58 +12:00
|
|
|
|
|
|
|
user =
|
|
|
|
User
|
|
|
|
|> Ash.Changeset.new()
|
2022-09-22 05:36:18 +12:00
|
|
|
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2021-07-25 03:28:58 +12:00
|
|
|
|
2024-03-28 09:52:28 +13:00
|
|
|
assert Ash.load!(user, :org).org.id == org.id
|
2021-07-25 03:28:58 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
test "loading context multitenant resources from attribute multitenant resources works" do
|
|
|
|
org =
|
|
|
|
Org
|
|
|
|
|> Ash.Changeset.new()
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2021-07-25 03:28:58 +12:00
|
|
|
|
2024-06-11 04:49:04 +12:00
|
|
|
user =
|
2021-07-25 03:28:58 +12:00
|
|
|
User
|
2024-06-11 04:49:04 +12:00
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "a"}, tenant: "org_#{org.id}")
|
2022-09-22 05:36:18 +12:00
|
|
|
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2021-07-25 03:28:58 +12:00
|
|
|
|
|
|
|
user2 =
|
|
|
|
User
|
2024-06-11 04:49:04 +12:00
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "a"}, tenant: "org_#{org.id}")
|
2022-09-22 05:36:18 +12:00
|
|
|
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2021-07-25 03:28:58 +12:00
|
|
|
|
2024-06-11 04:49:04 +12:00
|
|
|
post =
|
|
|
|
Post
|
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "foobar"},
|
|
|
|
authorize?: false,
|
|
|
|
tenant: "org_#{org.id}"
|
|
|
|
)
|
|
|
|
|> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove)
|
|
|
|
|> Ash.create!()
|
|
|
|
|
|
|
|
post_id = post.id
|
|
|
|
|
|
|
|
assert [%{posts: [%{id: ^post_id}]}, _] =
|
|
|
|
Ash.load!([user, user2], [posts: Ash.Query.limit(Post, 2)],
|
|
|
|
tenant: "org_#{org.id}",
|
|
|
|
authorize?: false
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
test "loading context multitenant resources across a many-to-many with a limit works" do
|
|
|
|
org =
|
|
|
|
Org
|
|
|
|
|> Ash.Changeset.new()
|
|
|
|
|> Ash.create!()
|
2021-07-25 03:28:58 +12:00
|
|
|
|
2024-06-11 04:49:04 +12:00
|
|
|
user =
|
|
|
|
User
|
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "a"}, tenant: "org_#{org.id}")
|
|
|
|
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|
|
|
|
|> Ash.create!()
|
|
|
|
|
|
|
|
post =
|
|
|
|
Post
|
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "foobar"},
|
|
|
|
authorize?: false,
|
|
|
|
tenant: "org_#{org.id}"
|
|
|
|
)
|
|
|
|
|> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove)
|
|
|
|
|> Ash.create!()
|
|
|
|
|
|
|
|
post2 =
|
|
|
|
Post
|
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "foobar"},
|
|
|
|
authorize?: false,
|
|
|
|
tenant: "org_#{org.id}"
|
|
|
|
)
|
|
|
|
|> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove)
|
|
|
|
|> Ash.Changeset.manage_relationship(:linked_posts, post, type: :append_and_remove)
|
|
|
|
|> Ash.create!()
|
|
|
|
|
|
|
|
post_id = post.id
|
|
|
|
|
|
|
|
assert [%{linked_posts: [%{id: ^post_id}]}, _] =
|
|
|
|
Ash.load!([post2, post], [linked_posts: Ash.Query.limit(Post, 2)],
|
|
|
|
tenant: "org_#{org.id}",
|
|
|
|
authorize?: false
|
|
|
|
)
|
2021-07-25 03:28:58 +12:00
|
|
|
end
|
|
|
|
|
2021-07-25 08:59:23 +12:00
|
|
|
test "manage_relationship from context multitenant resource to attribute multitenant resource doesn't raise an error" do
|
2024-03-28 09:52:28 +13:00
|
|
|
org = Org |> Ash.Changeset.new() |> Ash.create!()
|
|
|
|
user = User |> Ash.Changeset.new() |> Ash.create!()
|
2021-07-25 08:59:23 +12:00
|
|
|
|
|
|
|
Post
|
|
|
|
|> Ash.Changeset.for_create(:create, %{}, tenant: tenant(org))
|
2022-09-22 05:36:18 +12:00
|
|
|
|> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove)
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2021-07-25 08:59:23 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
test "loading attribute multitenant resources with limits from context multitenant resources works" do
|
|
|
|
org =
|
|
|
|
Org
|
|
|
|
|> Ash.Changeset.new()
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2021-07-25 08:59:23 +12:00
|
|
|
|
|
|
|
user =
|
|
|
|
User
|
|
|
|
|> Ash.Changeset.new()
|
2022-09-22 05:36:18 +12:00
|
|
|
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2021-07-25 08:59:23 +12:00
|
|
|
|
2024-03-28 09:52:28 +13:00
|
|
|
assert Ash.load!(user, :org).org.id == org.id
|
2021-07-25 08:59:23 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
test "loading context multitenant resources with limits from attribute multitenant resources works" do
|
|
|
|
org =
|
|
|
|
Org
|
|
|
|
|> Ash.Changeset.new()
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2021-07-25 08:59:23 +12:00
|
|
|
|
|
|
|
user1 =
|
|
|
|
User
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "a"})
|
2022-09-22 05:36:18 +12:00
|
|
|
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2021-07-25 08:59:23 +12:00
|
|
|
|
|
|
|
user2 =
|
|
|
|
User
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "b"})
|
2022-09-22 05:36:18 +12:00
|
|
|
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2021-07-25 08:59:23 +12:00
|
|
|
|
|
|
|
user1_id = user1.id
|
|
|
|
user2_id = user2.id
|
|
|
|
|
|
|
|
assert [%{id: ^user1_id}, %{id: ^user2_id}] =
|
2024-03-28 09:52:28 +13:00
|
|
|
Ash.load!(org, users: Ash.Query.sort(Ash.Query.limit(User, 10), :name)).users
|
2021-07-25 08:59:23 +12:00
|
|
|
end
|
|
|
|
|
2021-01-27 09:07:26 +13:00
|
|
|
test "unique constraints are properly scoped", %{org1: org1} do
|
|
|
|
post =
|
|
|
|
Post
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{})
|
2021-01-27 09:07:26 +13:00
|
|
|
|> Ash.Changeset.set_tenant(tenant(org1))
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2021-01-27 09:07:26 +13:00
|
|
|
|
|
|
|
assert_raise Ash.Error.Invalid,
|
|
|
|
~r/Invalid value provided for id: has already been taken/,
|
|
|
|
fn ->
|
|
|
|
Post
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{id: post.id})
|
2021-01-27 09:07:26 +13:00
|
|
|
|> Ash.Changeset.set_tenant(tenant(org1))
|
2024-03-28 09:52:28 +13:00
|
|
|
|> Ash.create!()
|
2021-01-27 09:07:26 +13:00
|
|
|
end
|
|
|
|
end
|
2020-10-29 15:26:45 +13:00
|
|
|
end
|