|
5 | 5 | defmodule Ash.ScopeTest do |
6 | 6 | use ExUnit.Case, async: true |
7 | 7 |
|
| 8 | + alias Ash.Test.Domain, as: Domain |
| 9 | + |
| 10 | + defmodule MyScope do |
| 11 | + defstruct [:actor, :tenant] |
| 12 | + |
| 13 | + defimpl Ash.Scope.ToOpts do |
| 14 | + def get_actor(%{actor: actor}), do: {:ok, actor} |
| 15 | + def get_tenant(%{tenant: tenant}), do: {:ok, tenant} |
| 16 | + def get_context(_), do: :error |
| 17 | + def get_tracer(_), do: :error |
| 18 | + def get_authorize?(_), do: {:ok, false} |
| 19 | + end |
| 20 | + end |
| 21 | + |
| 22 | + defmodule MultiTenantResource do |
| 23 | + use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Ets |
| 24 | + |
| 25 | + multitenancy do |
| 26 | + strategy :attribute |
| 27 | + attribute :tenant_id |
| 28 | + end |
| 29 | + |
| 30 | + ets do |
| 31 | + private?(true) |
| 32 | + end |
| 33 | + |
| 34 | + attributes do |
| 35 | + uuid_primary_key :id |
| 36 | + attribute :name, :string, public?: true |
| 37 | + attribute :tenant_id, :string, allow_nil?: false, public?: true |
| 38 | + end |
| 39 | + |
| 40 | + actions do |
| 41 | + default_accept :* |
| 42 | + defaults [:read, :destroy, create: :*, update: :*] |
| 43 | + end |
| 44 | + end |
| 45 | + |
8 | 46 | describe "Ash.Scope.to_opts/1 with Map" do |
9 | 47 | test "handles nested shared context pattern" do |
10 | 48 | context = %{ |
@@ -98,6 +136,62 @@ defmodule Ash.ScopeTest do |
98 | 136 | end |
99 | 137 | end |
100 | 138 |
|
| 139 | + describe "scope with update (issue #2662)" do |
| 140 | + test "scope tenant is used when record metadata has no tenant" do |
| 141 | + record = |
| 142 | + MultiTenantResource |
| 143 | + |> Ash.Changeset.for_create(:create, %{name: "original", tenant_id: "tenant_1"}, |
| 144 | + tenant: "tenant_1" |
| 145 | + ) |
| 146 | + |> Ash.create!() |
| 147 | + |
| 148 | + # Simulate a record whose metadata does not have a tenant set |
| 149 | + # (e.g. loaded from a context where tenant wasn't propagated) |
| 150 | + record = put_in(record.__metadata__[:tenant], nil) |
| 151 | + |
| 152 | + scope = %MyScope{actor: nil, tenant: "tenant_1"} |
| 153 | + |
| 154 | + assert {:ok, updated} = |
| 155 | + Ash.update(record, %{name: "updated"}, scope: scope) |
| 156 | + |
| 157 | + assert updated.name == "updated" |
| 158 | + end |
| 159 | + |
| 160 | + test "scope tenant is used via Ash.Scope.to_opts workaround" do |
| 161 | + record = |
| 162 | + MultiTenantResource |
| 163 | + |> Ash.Changeset.for_create(:create, %{name: "original", tenant_id: "tenant_1"}, |
| 164 | + tenant: "tenant_1" |
| 165 | + ) |
| 166 | + |> Ash.create!() |
| 167 | + |
| 168 | + record = put_in(record.__metadata__[:tenant], nil) |
| 169 | + |
| 170 | + scope = %MyScope{actor: nil, tenant: "tenant_1"} |
| 171 | + opts = Ash.Scope.to_opts(scope, action: :update) |
| 172 | + |
| 173 | + assert {:ok, updated} = Ash.update(record, %{name: "updated"}, opts) |
| 174 | + assert updated.name == "updated" |
| 175 | + end |
| 176 | + end |
| 177 | + |
| 178 | + describe "scope with destroy (issue #2662)" do |
| 179 | + test "scope tenant is used when record metadata has no tenant" do |
| 180 | + record = |
| 181 | + MultiTenantResource |
| 182 | + |> Ash.Changeset.for_create(:create, %{name: "to_delete", tenant_id: "tenant_1"}, |
| 183 | + tenant: "tenant_1" |
| 184 | + ) |
| 185 | + |> Ash.create!() |
| 186 | + |
| 187 | + record = put_in(record.__metadata__[:tenant], nil) |
| 188 | + |
| 189 | + scope = %MyScope{actor: nil, tenant: "tenant_1"} |
| 190 | + |
| 191 | + assert :ok = Ash.destroy(record, scope: scope) |
| 192 | + end |
| 193 | + end |
| 194 | + |
101 | 195 | describe "Ash.Context.to_opts/1 (deprecated)" do |
102 | 196 | test "delegates to Ash.Scope.to_opts/1" do |
103 | 197 | context = %{ |
|
0 commit comments