diff --git a/lib/diffo/provider/components/calculations/field_from_assignment.ex b/lib/diffo/provider/components/calculations/field_from_assignment.ex new file mode 100644 index 0000000..f3d4d4c --- /dev/null +++ b/lib/diffo/provider/components/calculations/field_from_assignment.ex @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2025 diffo contributors +# +# SPDX-License-Identifier: MIT + +defmodule Diffo.Provider.Calculations.FieldFromAssignment do + @moduledoc false + use Ash.Resource.Calculation + + @impl true + def load(_query, _opts, _context), do: [] + + @impl true + def calculate(records, opts, _context) do + alias_name = opts[:alias] + field = opts[:field] + + Enum.map(records, fn record -> + record.id + |> assignments(alias_name) + |> Enum.map(&Map.get(&1, field)) + end) + end + + defp assignments(id, nil) do + Diffo.Provider.AssignmentRelationship + |> Ash.Query.filter_input(target_id: id) + |> Ash.read!(domain: Diffo.Provider) + end + + defp assignments(id, alias_name) do + Diffo.Provider.AssignmentRelationship + |> Ash.Query.filter_input(target_id: id, alias: alias_name) + |> Ash.read!(domain: Diffo.Provider) + end +end diff --git a/test/provider/extension/field_from_assignment_test.exs b/test/provider/extension/field_from_assignment_test.exs new file mode 100644 index 0000000..270ee88 --- /dev/null +++ b/test/provider/extension/field_from_assignment_test.exs @@ -0,0 +1,124 @@ +# SPDX-FileCopyrightText: 2025 diffo contributors +# +# SPDX-License-Identifier: MIT + +defmodule Diffo.Provider.Extension.FieldFromAssignmentTest do + @moduledoc false + use ExUnit.Case, async: true + @moduletag :domain_extended + + alias Diffo.Provider.Assignment + alias Diffo.Test.Servo + + setup do + AshNeo4j.Sandbox.checkout() + on_exit(&AshNeo4j.Sandbox.rollback/0) + end + + defp setup_card do + updates = [ + card: [family: :ISAM, model: "EBLT48", technology: :adsl2Plus], + ports: [first: 1, last: 48, assignable_type: "ADSL2+"] + ] + + {:ok, card} = Servo.build_card(%{}) + {:ok, card} = Servo.define_card(card, %{characteristic_value_updates: updates}) + {:ok, card} = Servo.lifecycle_card(card, %{resource_state: :operating}) + card + end + + describe "FieldFromAssignment — aliased" do + test "returns field value from the aliased assignment record" do + card = setup_card() + {:ok, service} = Servo.build_access_service(%{}) + + {:ok, _card} = + Servo.assign_port(card, %{ + assignment: %Assignment{ + assignee_id: service.id, + operation: :auto_assign, + alias: :primary + } + }) + + service = Ash.load!(service, [:assigned_port], domain: Servo) + + assert length(service.assigned_port) == 1 + assert hd(service.assigned_port) == 1 + end + + test "returns empty list when no assignment exists" do + {:ok, service} = Servo.build_access_service(%{}) + + service = Ash.load!(service, [:assigned_port], domain: Servo) + + assert service.assigned_port == [] + end + + test "alias filters to only the matching assignment record" do + card_a = setup_card() + card_b = setup_card() + {:ok, service} = Servo.build_access_service(%{}) + + {:ok, _card_a} = + Servo.assign_port(card_a, %{ + assignment: %Assignment{ + assignee_id: service.id, + operation: :auto_assign, + alias: :primary + } + }) + + {:ok, _card_b} = + Servo.assign_port(card_b, %{ + assignment: %Assignment{ + assignee_id: service.id, + operation: :auto_assign, + alias: :secondary + } + }) + + service = Ash.load!(service, [:assigned_port], domain: Servo) + + assert length(service.assigned_port) == 1 + end + end + + describe "FieldFromAssignment — unaliased (all assignments)" do + test "returns field values from all assignment records" do + card_a = setup_card() + card_b = setup_card() + {:ok, service} = Servo.build_access_service(%{}) + + {:ok, _card_a} = + Servo.assign_port(card_a, %{ + assignment: %Assignment{ + assignee_id: service.id, + operation: :auto_assign, + alias: :primary + } + }) + + {:ok, _card_b} = + Servo.assign_port(card_b, %{ + assignment: %Assignment{ + assignee_id: service.id, + operation: :auto_assign, + alias: :secondary + } + }) + + service = Ash.load!(service, [:all_assignment_values], domain: Servo) + + assert length(service.all_assignment_values) == 2 + end + + test "returns empty list when no assignments exist" do + {:ok, service} = Servo.build_access_service(%{}) + + service = Ash.load!(service, [:all_assignment_values], domain: Servo) + + assert service.all_assignment_values == [] + end + end +end diff --git a/test/support/resource/instance/access_service.ex b/test/support/resource/instance/access_service.ex index defe2ab..42f702d 100644 --- a/test/support/resource/instance/access_service.ex +++ b/test/support/resource/instance/access_service.ex @@ -45,6 +45,12 @@ defmodule Diffo.Test.Instance.AccessService do calculate :assigner_names, {:array, :string}, {Diffo.Provider.Calculations.FieldViaAssignedRelationship, [field: :name]} + + calculate :assigned_port, {:array, :integer}, + {Diffo.Provider.Calculations.FieldFromAssignment, [alias: :primary, field: :value]} + + calculate :all_assignment_values, {:array, :integer}, + {Diffo.Provider.Calculations.FieldFromAssignment, [field: :value]} end actions do