From 4ab8f95b0f7a3250510333be9646bace4391062f Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Thu, 19 Mar 2026 23:44:53 +1030 Subject: [PATCH 01/13] initial vibe --- config/config.exs | 2 +- lib/nbn/nbn.ex | 82 ++++++ lib/nbn/resources/avc.ex | 88 +++++++ .../characteristic_values/avc_value.ex | 33 +++ .../characteristic_values/cvc_value.ex | 33 +++ .../nbn_ethernet_value.ex | 36 +++ .../characteristic_values/nni_group_value.ex | 33 +++ .../characteristic_values/nni_value.ex | 35 +++ .../characteristic_values/ntd_value.ex | 36 +++ .../characteristic_values/uni_value.ex | 36 +++ lib/nbn/resources/cvc.ex | 102 ++++++++ lib/nbn/resources/nbn_ethernet.ex | 89 +++++++ lib/nbn/resources/nni.ex | 89 +++++++ lib/nbn/resources/nni_group.ex | 102 ++++++++ lib/nbn/resources/ntd.ex | 88 +++++++ lib/nbn/resources/uni.ex | 89 +++++++ mix.exs | 2 +- mix.lock | 4 +- test/nbn/nbn_ethernet_test.exs | 240 ++++++++++++++++++ 19 files changed, 1215 insertions(+), 4 deletions(-) create mode 100644 lib/nbn/nbn.ex create mode 100644 lib/nbn/resources/avc.ex create mode 100644 lib/nbn/resources/characteristic_values/avc_value.ex create mode 100644 lib/nbn/resources/characteristic_values/cvc_value.ex create mode 100644 lib/nbn/resources/characteristic_values/nbn_ethernet_value.ex create mode 100644 lib/nbn/resources/characteristic_values/nni_group_value.ex create mode 100644 lib/nbn/resources/characteristic_values/nni_value.ex create mode 100644 lib/nbn/resources/characteristic_values/ntd_value.ex create mode 100644 lib/nbn/resources/characteristic_values/uni_value.ex create mode 100644 lib/nbn/resources/cvc.ex create mode 100644 lib/nbn/resources/nbn_ethernet.ex create mode 100644 lib/nbn/resources/nni.ex create mode 100644 lib/nbn/resources/nni_group.ex create mode 100644 lib/nbn/resources/ntd.ex create mode 100644 lib/nbn/resources/uni.ex create mode 100644 test/nbn/nbn_ethernet_test.exs diff --git a/config/config.exs b/config/config.exs index 053ae6b..ff4d1b4 100644 --- a/config/config.exs +++ b/config/config.exs @@ -37,5 +37,5 @@ config :spark, ] config :diffo, ash_domains: [Diffo.Provider] -config :diffo_example, ash_domains: [DiffoExample.Access] +config :diffo_example, ash_domains: [DiffoExample.Access, DiffoExample.Nbn] import_config "#{config_env()}.exs" diff --git a/lib/nbn/nbn.ex b/lib/nbn/nbn.ex new file mode 100644 index 0000000..8290ac6 --- /dev/null +++ b/lib/nbn/nbn.ex @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + Nbn - example NBN domain + + Models NBN network resources including the Ethernet circuit (NbnEthernet) + and its constituent resources: UNI (dedicated), AVC (dedicated), NTD, + CVC (aggregates AVCs, terminates at NNI Group), NNI Group, and NNI. + """ + use Ash.Domain, + otp_app: :diffo + + alias DiffoExample.Nbn.NbnEthernet + alias DiffoExample.Nbn.Uni + alias DiffoExample.Nbn.Avc + alias DiffoExample.Nbn.Ntd + alias DiffoExample.Nbn.Cvc + alias DiffoExample.Nbn.NniGroup + alias DiffoExample.Nbn.Nni + + domain do + description "An example showing how TMF Resources for a fictional NBN domain can be extended from the Provider domain" + end + + resources do + resource NbnEthernet do + define :get_nbn_ethernet_by_id, action: :read, get_by: :id + define :build_nbn_ethernet, action: :build + define :define_nbn_ethernet, action: :define + define :relate_nbn_ethernet, action: :relate + end + + resource Uni do + define :get_uni_by_id, action: :read, get_by: :id + define :build_uni, action: :build + define :define_uni, action: :define + define :relate_uni, action: :relate + end + + resource Avc do + define :get_avc_by_id, action: :read, get_by: :id + define :build_avc, action: :build + define :define_avc, action: :define + define :relate_avc, action: :relate + end + + resource Ntd do + define :get_ntd_by_id, action: :read, get_by: :id + define :build_ntd, action: :build + define :define_ntd, action: :define + define :relate_ntd, action: :relate + end + + resource Cvc do + define :get_cvc_by_id, action: :read, get_by: :id + define :build_cvc, action: :build + define :define_cvc, action: :define + define :assign_cvlan, action: :assign_cvlan + define :relate_cvc, action: :relate + end + + resource NniGroup do + define :get_nni_group_by_id, action: :read, get_by: :id + define :build_nni_group, action: :build + define :define_nni_group, action: :define + define :assign_svlan, action: :assign_svlan + define :relate_nni_group, action: :relate + end + + resource Nni do + define :get_nni_by_id, action: :read, get_by: :id + define :build_nni, action: :build + define :define_nni, action: :define + define :relate_nni, action: :relate + end + end +end diff --git a/lib/nbn/resources/avc.ex b/lib/nbn/resources/avc.ex new file mode 100644 index 0000000..8213d4b --- /dev/null +++ b/lib/nbn/resources/avc.ex @@ -0,0 +1,88 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.Avc do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + Avc - Access Virtual Circuit Resource Instance + + An AVC is the virtual circuit dedicated to an NBN Ethernet circuit, + carrying traffic between its related UNI and the CVC that aggregates it. + """ + + alias Diffo.Provider.BaseInstance + alias Diffo.Provider.Instance.Relationship + alias Diffo.Provider.Instance.Characteristic + alias Diffo.Provider.Instance.ActionHelper + + alias DiffoExample.Nbn + + use Ash.Resource, + fragments: [BaseInstance], + domain: Nbn + + resource do + description "An Ash Resource representing an Access Virtual Circuit (AVC)" + plural_name :Avcs + end + + specification do + id "b2c3d4e5-6f7a-4b8c-9d0e-1f2a3b4c5d6e" + name "avc" + type :resourceSpecification + description "An AVC Resource Instance dedicated to an NBN Ethernet circuit" + category "Network Resource" + end + + characteristics do + characteristic :avc, DiffoExample.Nbn.AvcValue + end + + actions do + create :build do + description "creates a new AVC resource instance" + accept [:id, :name, :type, :which] + argument :specified_by, :uuid, public?: false + argument :relationships, {:array, :struct} + argument :features, {:array, :uuid}, public?: false + argument :characteristics, {:array, :uuid}, public?: false + argument :places, {:array, :struct} + argument :parties, {:array, :struct} + + change set_attribute(:type, :resource) + + change before_action(fn changeset, _context -> ActionHelper.build_before(changeset) end) + + change after_action(fn changeset, result, _context -> + ActionHelper.build_after(changeset, result, Nbn, :get_avc_by_id) + end) + + change load [:href] + upsert? false + end + + update :define do + description "defines the AVC" + argument :characteristic_value_updates, {:array, :term} + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Characteristic.update_values(result, changeset), + {:ok, result} <- Nbn.get_avc_by_id(result.id), + do: {:ok, result} + end) + end + + update :relate do + description "relates the AVC with other instances" + argument :relationships, {:array, :struct} + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Relationship.relate_instance(result, changeset), + {:ok, result} <- Nbn.get_avc_by_id(result.id), + do: {:ok, result} + end) + end + end +end diff --git a/lib/nbn/resources/characteristic_values/avc_value.ex b/lib/nbn/resources/characteristic_values/avc_value.ex new file mode 100644 index 0000000..f24266a --- /dev/null +++ b/lib/nbn/resources/characteristic_values/avc_value.ex @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.AvcValue do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + AvcValue - AshTyped Struct for AVC Characteristic Value + """ + use Ash.TypedStruct, extensions: [AshJason.TypedStruct, AshOutstanding.TypedStruct] + + jason do + pick [:cir, :pir] + compact(true) + end + + outstanding do + expect [:cir, :pir] + end + + typed_struct do + field :cir, :integer, description: "Committed Information Rate in Mbps" + + field :pir, :integer, description: "Peak Information Rate in Mbps" + end + + defimpl String.Chars do + def to_string(struct) do + inspect(struct) + end + end +end diff --git a/lib/nbn/resources/characteristic_values/cvc_value.ex b/lib/nbn/resources/characteristic_values/cvc_value.ex new file mode 100644 index 0000000..dc68a2e --- /dev/null +++ b/lib/nbn/resources/characteristic_values/cvc_value.ex @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.CvcValue do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + CvcValue - AshTyped Struct for Connectivity Virtual Circuit Characteristic Value + """ + use Ash.TypedStruct, extensions: [AshJason.TypedStruct, AshOutstanding.TypedStruct] + + jason do + pick [:cvc_id, :bandwidth] + compact(true) + end + + outstanding do + expect [:cvc_id, :bandwidth] + end + + typed_struct do + field :cvc_id, :string, description: "the unique CVC identifier" + + field :bandwidth, :integer, description: "total CVC bandwidth in Mbps" + end + + defimpl String.Chars do + def to_string(struct) do + inspect(struct) + end + end +end diff --git a/lib/nbn/resources/characteristic_values/nbn_ethernet_value.ex b/lib/nbn/resources/characteristic_values/nbn_ethernet_value.ex new file mode 100644 index 0000000..57e0d27 --- /dev/null +++ b/lib/nbn/resources/characteristic_values/nbn_ethernet_value.ex @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.NbnEthernetValue do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + NbnEthernetValue - AshTyped Struct for NBN Ethernet Circuit Characteristic Value + """ + use Ash.TypedStruct, extensions: [AshJason.TypedStruct, AshOutstanding.TypedStruct] + + jason do + pick [:circuit_id, :speed, :technology] + compact(true) + end + + outstanding do + expect [:circuit_id, :speed] + end + + typed_struct do + field :circuit_id, :string, description: "the unique NBN circuit identifier" + + field :speed, :integer, description: "the circuit download speed in Mbps" + + field :technology, :atom, + description: "the access technology (:FTTP, :FTTN, :HFC, :Fixed_Wireless)" + end + + defimpl String.Chars do + def to_string(struct) do + inspect(struct) + end + end +end diff --git a/lib/nbn/resources/characteristic_values/nni_group_value.ex b/lib/nbn/resources/characteristic_values/nni_group_value.ex new file mode 100644 index 0000000..bed82c3 --- /dev/null +++ b/lib/nbn/resources/characteristic_values/nni_group_value.ex @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.NniGroupValue do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + NniGroupValue - AshTyped Struct for NNI Group Characteristic Value + """ + use Ash.TypedStruct, extensions: [AshJason.TypedStruct, AshOutstanding.TypedStruct] + + jason do + pick [:name, :location] + compact(true) + end + + outstanding do + expect [:name, :location] + end + + typed_struct do + field :name, :string, description: "the NNI group name" + + field :location, :string, description: "the Point of Interconnect (PoI) location" + end + + defimpl String.Chars do + def to_string(struct) do + inspect(struct) + end + end +end diff --git a/lib/nbn/resources/characteristic_values/nni_value.ex b/lib/nbn/resources/characteristic_values/nni_value.ex new file mode 100644 index 0000000..e3028fa --- /dev/null +++ b/lib/nbn/resources/characteristic_values/nni_value.ex @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.NniValue do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + NniValue - AshTyped Struct for NNI Characteristic Value + """ + use Ash.TypedStruct, extensions: [AshJason.TypedStruct, AshOutstanding.TypedStruct] + + jason do + pick [:port_id, :capacity, :technology] + compact(true) + end + + outstanding do + expect [:port_id, :capacity] + end + + typed_struct do + field :port_id, :string, description: "the NNI port identifier" + + field :capacity, :integer, description: "the NNI port capacity in Gbps" + + field :technology, :atom, description: "the NNI technology (:Ethernet, :Fibre)" + end + + defimpl String.Chars do + def to_string(struct) do + inspect(struct) + end + end +end diff --git a/lib/nbn/resources/characteristic_values/ntd_value.ex b/lib/nbn/resources/characteristic_values/ntd_value.ex new file mode 100644 index 0000000..85ee77b --- /dev/null +++ b/lib/nbn/resources/characteristic_values/ntd_value.ex @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.NtdValue do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + NtdValue - AshTyped Struct for NTD Characteristic Value + """ + use Ash.TypedStruct, extensions: [AshJason.TypedStruct, AshOutstanding.TypedStruct] + + jason do + pick [:model, :serial_number, :technology] + compact(true) + end + + outstanding do + expect [:model, :serial_number] + end + + typed_struct do + field :model, :string, description: "the NTD device model" + + field :serial_number, :string, description: "the NTD serial number" + + field :technology, :atom, + description: "the access technology (:FTTP, :FTTN, :HFC, :Fixed_Wireless)" + end + + defimpl String.Chars do + def to_string(struct) do + inspect(struct) + end + end +end diff --git a/lib/nbn/resources/characteristic_values/uni_value.ex b/lib/nbn/resources/characteristic_values/uni_value.ex new file mode 100644 index 0000000..56e8870 --- /dev/null +++ b/lib/nbn/resources/characteristic_values/uni_value.ex @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.UniValue do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + UniValue - AshTyped Struct for UNI Characteristic Value + """ + use Ash.TypedStruct, extensions: [AshJason.TypedStruct, AshOutstanding.TypedStruct] + + jason do + pick [:vlan_id, :bandwidth_profile, :technology] + compact(true) + end + + outstanding do + expect [:vlan_id, :technology] + end + + typed_struct do + field :vlan_id, :integer, description: "the VLAN ID for the UNI" + + field :bandwidth_profile, :string, description: "the bandwidth profile name for the UNI" + + field :technology, :atom, + description: "the access technology (:FTTP, :FTTN, :HFC, :Fixed_Wireless)" + end + + defimpl String.Chars do + def to_string(struct) do + inspect(struct) + end + end +end diff --git a/lib/nbn/resources/cvc.ex b/lib/nbn/resources/cvc.ex new file mode 100644 index 0000000..ef17ccd --- /dev/null +++ b/lib/nbn/resources/cvc.ex @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.Cvc do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + Cvc - Connectivity Virtual Circuit Resource Instance + + A CVC is the wholesale bandwidth product that aggregates one or more AVC + resources and terminates at an NNI Group resource. Each AVC has a related UNI. + """ + + alias Diffo.Provider.BaseInstance + alias Diffo.Provider.Instance.Relationship + alias Diffo.Provider.Instance.Characteristic + alias Diffo.Provider.Instance.ActionHelper + alias Diffo.Provider.Assigner + alias Diffo.Provider.Assignment + + alias DiffoExample.Nbn + + use Ash.Resource, + fragments: [BaseInstance], + domain: Nbn + + resource do + description "An Ash Resource representing a Connectivity Virtual Circuit (CVC)" + plural_name :Cvcs + end + + specification do + id "d4e5f6a7-8b9c-4d0e-bf1a-3b4c5d6e7f8a" + name "cvc" + type :resourceSpecification + description "A Connectivity Virtual Circuit Resource Instance that aggregates AVCs and terminates at an NNI Group" + category "Network Resource" + end + + characteristics do + characteristic :cvc, DiffoExample.Nbn.CvcValue + characteristic :cvlan_ids, Diffo.Provider.AssignableValue + end + + actions do + create :build do + description "creates a new CVC resource instance" + accept [:id, :name, :type, :which] + argument :specified_by, :uuid, public?: false + argument :relationships, {:array, :struct} + argument :features, {:array, :uuid}, public?: false + argument :characteristics, {:array, :uuid}, public?: false + argument :places, {:array, :struct} + argument :parties, {:array, :struct} + + change set_attribute(:type, :resource) + + change before_action(fn changeset, _context -> ActionHelper.build_before(changeset) end) + + change after_action(fn changeset, result, _context -> + ActionHelper.build_after(changeset, result, Nbn, :get_cvc_by_id) + end) + + change load [:href] + upsert? false + end + + update :define do + description "defines the CVC" + argument :characteristic_value_updates, {:array, :term} + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Characteristic.update_values(result, changeset), + {:ok, result} <- Nbn.get_cvc_by_id(result.id), + do: {:ok, result} + end) + end + + update :assign_cvlan do + description "assigns a C-VLAN ID from the CVC pool to an AVC" + argument :assignment, :struct, constraints: [instance_of: Assignment] + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Assigner.assign(result, changeset, :cvlan_ids, :cvlan_id), + {:ok, result} <- Nbn.get_cvc_by_id(result.id), + do: {:ok, result} + end) + end + + update :relate do + description "relates the CVC with other instances (e.g. AVC aggregation, NNI Group termination)" + argument :relationships, {:array, :struct} + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Relationship.relate_instance(result, changeset), + {:ok, result} <- Nbn.get_cvc_by_id(result.id), + do: {:ok, result} + end) + end + end +end diff --git a/lib/nbn/resources/nbn_ethernet.ex b/lib/nbn/resources/nbn_ethernet.ex new file mode 100644 index 0000000..aee6dbf --- /dev/null +++ b/lib/nbn/resources/nbn_ethernet.ex @@ -0,0 +1,89 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.NbnEthernet do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + NbnEthernet - NBN Ethernet Circuit Resource Instance + + An NBN Ethernet circuit comprising a dedicated UNI and AVC resource. + The circuit is related to its UNI, which in turn is aggregated by a CVC + that terminates at an NNI Group. + """ + + alias Diffo.Provider.BaseInstance + alias Diffo.Provider.Instance.Relationship + alias Diffo.Provider.Instance.Characteristic + alias Diffo.Provider.Instance.ActionHelper + + alias DiffoExample.Nbn + + use Ash.Resource, + fragments: [BaseInstance], + domain: Nbn + + resource do + description "An Ash Resource representing an NBN Ethernet Circuit" + plural_name :NbnEthernets + end + + specification do + id "f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c" + name "nbnEthernet" + type :resourceSpecification + description "An NBN Ethernet Circuit comprising a dedicated UNI and AVC" + category "Network Resource" + end + + characteristics do + characteristic :nbn_ethernet, DiffoExample.Nbn.NbnEthernetValue + end + + actions do + create :build do + description "creates a new NBN Ethernet circuit resource instance" + accept [:id, :name, :type, :which] + argument :specified_by, :uuid, public?: false + argument :relationships, {:array, :struct} + argument :features, {:array, :uuid}, public?: false + argument :characteristics, {:array, :uuid}, public?: false + argument :places, {:array, :struct} + argument :parties, {:array, :struct} + + change set_attribute(:type, :resource) + + change before_action(fn changeset, _context -> ActionHelper.build_before(changeset) end) + + change after_action(fn changeset, result, _context -> + ActionHelper.build_after(changeset, result, Nbn, :get_nbn_ethernet_by_id) + end) + + change load [:href] + upsert? false + end + + update :define do + description "defines the NBN Ethernet circuit" + argument :characteristic_value_updates, {:array, :term} + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Characteristic.update_values(result, changeset), + {:ok, result} <- Nbn.get_nbn_ethernet_by_id(result.id), + do: {:ok, result} + end) + end + + update :relate do + description "relates the NBN Ethernet circuit with other instances (e.g. UNI)" + argument :relationships, {:array, :struct} + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Relationship.relate_instance(result, changeset), + {:ok, result} <- Nbn.get_nbn_ethernet_by_id(result.id), + do: {:ok, result} + end) + end + end +end diff --git a/lib/nbn/resources/nni.ex b/lib/nbn/resources/nni.ex new file mode 100644 index 0000000..81e070a --- /dev/null +++ b/lib/nbn/resources/nni.ex @@ -0,0 +1,89 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.Nni do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + Nni - Network-to-Network Interface Resource Instance + + An NNI is the physical handover port between the NBN network and the + Retail Service Provider (RSP). Multiple NNI resources are grouped within + an NNI Group resource. + """ + + alias Diffo.Provider.BaseInstance + alias Diffo.Provider.Instance.Relationship + alias Diffo.Provider.Instance.Characteristic + alias Diffo.Provider.Instance.ActionHelper + + alias DiffoExample.Nbn + + use Ash.Resource, + fragments: [BaseInstance], + domain: Nbn + + resource do + description "An Ash Resource representing a Network-to-Network Interface (NNI)" + plural_name :Nnis + end + + specification do + id "f6a7b8c9-0d1e-4f2a-9b3c-5d6e7f8a9b0c" + name "nni" + type :resourceSpecification + description "An NNI Resource Instance that is part of an NNI Group" + category "Network Resource" + end + + characteristics do + characteristic :nni, DiffoExample.Nbn.NniValue + end + + actions do + create :build do + description "creates a new NNI resource instance" + accept [:id, :name, :type, :which] + argument :specified_by, :uuid, public?: false + argument :relationships, {:array, :struct} + argument :features, {:array, :uuid}, public?: false + argument :characteristics, {:array, :uuid}, public?: false + argument :places, {:array, :struct} + argument :parties, {:array, :struct} + + change set_attribute(:type, :resource) + + change before_action(fn changeset, _context -> ActionHelper.build_before(changeset) end) + + change after_action(fn changeset, result, _context -> + ActionHelper.build_after(changeset, result, Nbn, :get_nni_by_id) + end) + + change load [:href] + upsert? false + end + + update :define do + description "defines the NNI" + argument :characteristic_value_updates, {:array, :term} + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Characteristic.update_values(result, changeset), + {:ok, result} <- Nbn.get_nni_by_id(result.id), + do: {:ok, result} + end) + end + + update :relate do + description "relates the NNI with other instances (e.g. its parent NNI Group)" + argument :relationships, {:array, :struct} + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Relationship.relate_instance(result, changeset), + {:ok, result} <- Nbn.get_nni_by_id(result.id), + do: {:ok, result} + end) + end + end +end diff --git a/lib/nbn/resources/nni_group.ex b/lib/nbn/resources/nni_group.ex new file mode 100644 index 0000000..630fdc5 --- /dev/null +++ b/lib/nbn/resources/nni_group.ex @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.NniGroup do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + NniGroup - NNI Group Resource Instance + + An NNI Group is the Point of Interconnect (PoI) grouping where a CVC + terminates. It comprises multiple NNI resources. + """ + + alias Diffo.Provider.BaseInstance + alias Diffo.Provider.Instance.Relationship + alias Diffo.Provider.Instance.Characteristic + alias Diffo.Provider.Instance.ActionHelper + alias Diffo.Provider.Assigner + alias Diffo.Provider.Assignment + + alias DiffoExample.Nbn + + use Ash.Resource, + fragments: [BaseInstance], + domain: Nbn + + resource do + description "An Ash Resource representing an NNI Group" + plural_name :NniGroups + end + + specification do + id "e5f6a7b8-9c0d-4e1f-8a2b-4c5d6e7f8a9b" + name "nniGroup" + type :resourceSpecification + description "An NNI Group Resource Instance comprising multiple NNI resources" + category "Network Resource" + end + + characteristics do + characteristic :nni_group, DiffoExample.Nbn.NniGroupValue + characteristic :svlan_ids, Diffo.Provider.AssignableValue + end + + actions do + create :build do + description "creates a new NNI Group resource instance" + accept [:id, :name, :type, :which] + argument :specified_by, :uuid, public?: false + argument :relationships, {:array, :struct} + argument :features, {:array, :uuid}, public?: false + argument :characteristics, {:array, :uuid}, public?: false + argument :places, {:array, :struct} + argument :parties, {:array, :struct} + + change set_attribute(:type, :resource) + + change before_action(fn changeset, _context -> ActionHelper.build_before(changeset) end) + + change after_action(fn changeset, result, _context -> + ActionHelper.build_after(changeset, result, Nbn, :get_nni_group_by_id) + end) + + change load [:href] + upsert? false + end + + update :define do + description "defines the NNI Group" + argument :characteristic_value_updates, {:array, :term} + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Characteristic.update_values(result, changeset), + {:ok, result} <- Nbn.get_nni_group_by_id(result.id), + do: {:ok, result} + end) + end + + update :assign_svlan do + description "assigns an S-VLAN ID from the NNI Group pool to a CVC" + argument :assignment, :struct, constraints: [instance_of: Assignment] + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Assigner.assign(result, changeset, :svlan_ids, :svlan_id), + {:ok, result} <- Nbn.get_nni_group_by_id(result.id), + do: {:ok, result} + end) + end + + update :relate do + description "relates the NNI Group with other instances (e.g. NNI resources it comprises)" + argument :relationships, {:array, :struct} + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Relationship.relate_instance(result, changeset), + {:ok, result} <- Nbn.get_nni_group_by_id(result.id), + do: {:ok, result} + end) + end + end +end diff --git a/lib/nbn/resources/ntd.ex b/lib/nbn/resources/ntd.ex new file mode 100644 index 0000000..6ef9617 --- /dev/null +++ b/lib/nbn/resources/ntd.ex @@ -0,0 +1,88 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.Ntd do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + Ntd - Network Termination Device Resource Instance + + An NTD is the device installed at the customer premises that connects + the premises to the NBN network. It is related to a UNI resource. + """ + + alias Diffo.Provider.BaseInstance + alias Diffo.Provider.Instance.Relationship + alias Diffo.Provider.Instance.Characteristic + alias Diffo.Provider.Instance.ActionHelper + + alias DiffoExample.Nbn + + use Ash.Resource, + fragments: [BaseInstance], + domain: Nbn + + resource do + description "An Ash Resource representing a Network Termination Device (NTD)" + plural_name :Ntds + end + + specification do + id "c3d4e5f6-7a8b-4c9d-ae0f-2a3b4c5d6e7f" + name "ntd" + type :resourceSpecification + description "An NTD Resource Instance related to a UNI" + category "Network Resource" + end + + characteristics do + characteristic :ntd, DiffoExample.Nbn.NtdValue + end + + actions do + create :build do + description "creates a new NTD resource instance" + accept [:id, :name, :type, :which] + argument :specified_by, :uuid, public?: false + argument :relationships, {:array, :struct} + argument :features, {:array, :uuid}, public?: false + argument :characteristics, {:array, :uuid}, public?: false + argument :places, {:array, :struct} + argument :parties, {:array, :struct} + + change set_attribute(:type, :resource) + + change before_action(fn changeset, _context -> ActionHelper.build_before(changeset) end) + + change after_action(fn changeset, result, _context -> + ActionHelper.build_after(changeset, result, Nbn, :get_ntd_by_id) + end) + + change load [:href] + upsert? false + end + + update :define do + description "defines the NTD" + argument :characteristic_value_updates, {:array, :term} + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Characteristic.update_values(result, changeset), + {:ok, result} <- Nbn.get_ntd_by_id(result.id), + do: {:ok, result} + end) + end + + update :relate do + description "relates the NTD with other instances (e.g. UNI)" + argument :relationships, {:array, :struct} + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Relationship.relate_instance(result, changeset), + {:ok, result} <- Nbn.get_ntd_by_id(result.id), + do: {:ok, result} + end) + end + end +end diff --git a/lib/nbn/resources/uni.ex b/lib/nbn/resources/uni.ex new file mode 100644 index 0000000..2408411 --- /dev/null +++ b/lib/nbn/resources/uni.ex @@ -0,0 +1,89 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.Uni do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + Uni - User Network Interface Resource Instance + + A UNI is the physical/logical interface at the customer premises. It is + related to an NTD resource and to its parent NBN Ethernet circuit. + It is related to an AVC resource, which is in turn aggregated by a CVC. + """ + + alias Diffo.Provider.BaseInstance + alias Diffo.Provider.Instance.Relationship + alias Diffo.Provider.Instance.Characteristic + alias Diffo.Provider.Instance.ActionHelper + + alias DiffoExample.Nbn + + use Ash.Resource, + fragments: [BaseInstance], + domain: Nbn + + resource do + description "An Ash Resource representing a User Network Interface (UNI)" + plural_name :Unis + end + + specification do + id "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d" + name "uni" + type :resourceSpecification + description "A UNI Resource Instance related to an NTD and an NBN Ethernet circuit" + category "Network Resource" + end + + characteristics do + characteristic :uni, DiffoExample.Nbn.UniValue + end + + actions do + create :build do + description "creates a new UNI resource instance" + accept [:id, :name, :type, :which] + argument :specified_by, :uuid, public?: false + argument :relationships, {:array, :struct} + argument :features, {:array, :uuid}, public?: false + argument :characteristics, {:array, :uuid}, public?: false + argument :places, {:array, :struct} + argument :parties, {:array, :struct} + + change set_attribute(:type, :resource) + + change before_action(fn changeset, _context -> ActionHelper.build_before(changeset) end) + + change after_action(fn changeset, result, _context -> + ActionHelper.build_after(changeset, result, Nbn, :get_uni_by_id) + end) + + change load [:href] + upsert? false + end + + update :define do + description "defines the UNI" + argument :characteristic_value_updates, {:array, :term} + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Characteristic.update_values(result, changeset), + {:ok, result} <- Nbn.get_uni_by_id(result.id), + do: {:ok, result} + end) + end + + update :relate do + description "relates the UNI with other instances (e.g. NTD, NBN Ethernet circuit)" + argument :relationships, {:array, :struct} + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Relationship.relate_instance(result, changeset), + {:ok, result} <- Nbn.get_uni_by_id(result.id), + do: {:ok, result} + end) + end + end +end diff --git a/mix.exs b/mix.exs index ce6cbe8..ab342bd 100644 --- a/mix.exs +++ b/mix.exs @@ -78,7 +78,7 @@ defmodule DiffoExample.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:diffo, diffo_version("~> 0.1.5")}, + {:diffo, diffo_version("~> 0.1.6")}, {:igniter, "~> 0.6", only: [:dev, :test]}, {:ex_doc, "~> 0.37", only: [:dev, :test], runtime: false} ] diff --git a/mix.lock b/mix.lock index 34987a8..7134cf1 100644 --- a/mix.lock +++ b/mix.lock @@ -1,14 +1,14 @@ %{ "ash": {:hex, :ash, "3.19.3", "58b1bb3aea3d1d45d1c990059ffd0753409cc92fc4afe387376cb155e2a8c2a0", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.14 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "94b628319f2e144affaf1f8008277bad3340a198d48e6d2ed372990ac1643f9b"}, "ash_jason": {:hex, :ash_jason, "3.1.0", "84a88dfe5e25a20d55cf2d2664885cd086fa45871e8777aedc3ad96a282e2a6f", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:spark, ">= 2.1.21 and < 3.0.0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "71e6bbc421fb2cf7079f8804814145cca458116c839fc798f9606b806e07eb2b"}, - "ash_neo4j": {:hex, :ash_neo4j, "0.2.14", "c6bb1b895510ab423afc4225840d4c4e94716490316bf75aa8da1da464f53e0a", [:mix], [{:ash, ">= 3.19.1 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:bolty, ">= 0.0.7", [hex: :bolty, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "888bd4a9fcc3c231642dfb4cb9110d9aef8a57ff61f16f078bfd278cde17cd69"}, + "ash_neo4j": {:hex, :ash_neo4j, "0.2.15", "1380f3a3f6b43fb346308808d621bd19b891b859431fd4b1ac26472fcee5b372", [:mix], [{:ash, ">= 3.19.1 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:bolty, ">= 0.0.7", [hex: :bolty, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8be0a84e02ce70d691b3619c1f7cb7c8e1d9daf001673a227b27762f3690ac40"}, "ash_outstanding": {:hex, :ash_outstanding, "0.2.4", "c72b91f1b8e4859fb033eddf66d0ba36cfd8af0c2a9748c7ef9e6ccfdb5d093d", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:outstanding, "~> 0.2.4", [hex: :outstanding, repo: "hexpm", optional: false]}], "hexpm", "64ba8f582ce69c9050352c75f0895db186c7a56f35039dab34c8e1ab7516f9ce"}, "ash_state_machine": {:hex, :ash_state_machine, "0.2.12", "c0f7ebb8a176584f70c6ed196b7d0118c930d73e0590ade705d2dddc48aa7311", [:mix], [{:ash, ">= 3.4.66 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}], "hexpm", "394ce761ce82358e3c715e1cae6c5cf1390be27c03a8b661f2e5a2fda849873d"}, "bolty": {:hex, :bolty, "0.0.7", "257889f71bd16a9291bda8290c1018123c4084c8761f905b97d7176e65d8b111", [:mix], [{:db_connection, "~> 2.7.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "fc8f88b86d292ad1336f9e246a6c0452e64774b38fac0351c141549420946e5e"}, "crux": {:hex, :crux, "0.1.2", "4441c9e3a34f1e340954ce96b9ad5a2de13ceb4f97b3f910211227bb92e2ca90", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "563ea3748ebfba9cc078e6d198a1d6a06015a8fae503f0b721363139f0ddb350"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, - "diffo": {:hex, :diffo, "0.1.5", "e37d32763bd3a9b6a984523fb4f8ef9310266349576523c1d3085d7a1b8234c7", [:mix], [{:ash, ">= 3.19.1 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_jason, "~> 3.0", [hex: :ash_jason, repo: "hexpm", optional: false]}, {:ash_neo4j, "~> 0.2.14", [hex: :ash_neo4j, repo: "hexpm", optional: false]}, {:ash_outstanding, "~> 0.2.3", [hex: :ash_outstanding, repo: "hexpm", optional: false]}, {:ash_state_machine, "~> 0.2.12", [hex: :ash_state_machine, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm", "761538f03f2f79c3170413925cf8b42545063303e5c7f4a0d1923bb964528161"}, + "diffo": {:hex, :diffo, "0.1.6", "12325449e4c17e1d2e14d821387cebcd2167542e3efccf3af15cc3ef99ef425f", [:mix], [{:ash, ">= 3.19.1 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_jason, "~> 3.0", [hex: :ash_jason, repo: "hexpm", optional: false]}, {:ash_neo4j, "~> 0.2.15", [hex: :ash_neo4j, repo: "hexpm", optional: false]}, {:ash_outstanding, "~> 0.2.3", [hex: :ash_outstanding, repo: "hexpm", optional: false]}, {:ash_state_machine, "~> 0.2.12", [hex: :ash_state_machine, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm", "ef07a854021bb8a86ba38d59cdf6fdd4fbc00e0e01fdc0bc7cf9c606977ce9f8"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, diff --git a/test/nbn/nbn_ethernet_test.exs b/test/nbn/nbn_ethernet_test.exs new file mode 100644 index 0000000..5b3e0d0 --- /dev/null +++ b/test/nbn/nbn_ethernet_test.exs @@ -0,0 +1,240 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.NbnEthernetTest do + @moduledoc false + use ExUnit.Case + alias Diffo.Provider.Specification + alias Diffo.Provider.Characteristic + alias DiffoExample.Nbn + alias DiffoExample.Nbn.NbnEthernet + alias DiffoExample.Nbn.Uni + alias DiffoExample.Nbn.Avc + alias DiffoExample.Nbn.Ntd + alias DiffoExample.Nbn.Cvc + alias DiffoExample.Nbn.NniGroup + alias DiffoExample.Nbn.Nni + alias DiffoExample.Test.Characteristics + + setup_all do + AshNeo4j.BoltyHelper.start() + end + + setup do + on_exit(fn -> + AshNeo4j.Neo4jHelper.delete_all() + end) + end + + describe "build nbn_ethernet" do + test "create an nbn_ethernet circuit" do + {:ok, circuit} = Nbn.build_nbn_ethernet(%{}) + + # check the instance is an NbnEthernet + assert is_struct(circuit, NbnEthernet) + + # check specification resource enrichment and node relationship + refute is_nil(circuit.specification_id) + assert is_struct(circuit.specification, Specification) + + assert AshNeo4j.Neo4jHelper.nodes_relate_how?( + :Instance, + %{uuid: circuit.id}, + :Specification, + %{uuid: circuit.specification_id}, + :SPECIFIED_BY, + :outgoing + ) + + # check characteristic resource enrichment and node relationships + assert is_list(circuit.characteristics) + assert length(circuit.characteristics) == 1 + + Enum.each(circuit.characteristics, fn characteristic -> + assert is_struct(characteristic, Characteristic) + + assert AshNeo4j.Neo4jHelper.nodes_relate_how?( + :Instance, + %{uuid: circuit.id}, + :Characteristic, + %{uuid: characteristic.id}, + :HAS, + :outgoing + ) + end) + + encoding = Jason.encode!(circuit) |> Diffo.Util.summarise_dates() + + assert encoding == + ~s({"id":"#{circuit.id}","href":"resourceInventoryManagement/v4/resource/nbnEthernet/#{circuit.id}","category":"Network Resource","resourceSpecification":{"id":"f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","href":"resourceCatalogManagement/v4/resourceSpecification/f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","name":"nbnEthernet","version":"v1.0.0"},"resourceCharacteristic":[{"name":"nbn_ethernet","value":{}}]}) + end + + test "define nbn_ethernet circuit" do + {:ok, circuit} = Nbn.build_nbn_ethernet(%{}) + + updates = [ + nbn_ethernet: [circuit_id: "NBN-CID-123456", speed: 1000, technology: :FTTP] + ] + + {:ok, circuit} = Nbn.define_nbn_ethernet(circuit, %{characteristic_value_updates: updates}) + + Characteristics.check_values( + [nbn_ethernet: [circuit_id: "NBN-CID-123456", speed: 1000, technology: :FTTP]], + circuit + ) + end + end + + describe "build uni" do + test "create a uni" do + {:ok, uni} = Nbn.build_uni(%{}) + + assert is_struct(uni, Uni) + refute is_nil(uni.specification_id) + assert is_struct(uni.specification, Specification) + assert is_list(uni.characteristics) + assert length(uni.characteristics) == 1 + end + + test "define uni" do + {:ok, uni} = Nbn.build_uni(%{}) + + updates = [ + uni: [vlan_id: 101, bandwidth_profile: "TC4", technology: :FTTP] + ] + + {:ok, uni} = Nbn.define_uni(uni, %{characteristic_value_updates: updates}) + + Characteristics.check_values( + [uni: [vlan_id: 101, bandwidth_profile: "TC4", technology: :FTTP]], + uni + ) + end + end + + describe "build avc" do + test "create an avc" do + {:ok, avc} = Nbn.build_avc(%{}) + + assert is_struct(avc, Avc) + refute is_nil(avc.specification_id) + assert is_struct(avc.specification, Specification) + assert is_list(avc.characteristics) + assert length(avc.characteristics) == 1 + end + + test "define avc" do + {:ok, avc} = Nbn.build_avc(%{}) + + updates = [ + avc: [cir: 100, pir: 1000] + ] + + {:ok, avc} = Nbn.define_avc(avc, %{characteristic_value_updates: updates}) + + Characteristics.check_values( + [avc: [cir: 100, pir: 1000]], + avc + ) + end + end + + describe "build ntd" do + test "create an ntd" do + {:ok, ntd} = Nbn.build_ntd(%{}) + + assert is_struct(ntd, Ntd) + refute is_nil(ntd.specification_id) + end + + test "define ntd" do + {:ok, ntd} = Nbn.build_ntd(%{}) + + updates = [ + ntd: [model: "Arris CM8200", serial_number: "SN-ABC-123456", technology: :HFC] + ] + + {:ok, ntd} = Nbn.define_ntd(ntd, %{characteristic_value_updates: updates}) + + Characteristics.check_values( + [ntd: [model: "Arris CM8200", serial_number: "SN-ABC-123456", technology: :HFC]], + ntd + ) + end + end + + describe "build cvc" do + test "create a cvc" do + {:ok, cvc} = Nbn.build_cvc(%{}) + + assert is_struct(cvc, Cvc) + refute is_nil(cvc.specification_id) + end + + test "define cvc" do + {:ok, cvc} = Nbn.build_cvc(%{}) + + updates = [ + cvc: [cvc_id: "CVC-POI-SYD-001", bandwidth: 10000] + ] + + {:ok, cvc} = Nbn.define_cvc(cvc, %{characteristic_value_updates: updates}) + + Characteristics.check_values( + [cvc: [cvc_id: "CVC-POI-SYD-001", bandwidth: 10000]], + cvc + ) + end + end + + describe "build nni_group" do + test "create an nni_group" do + {:ok, nni_group} = Nbn.build_nni_group(%{}) + + assert is_struct(nni_group, NniGroup) + refute is_nil(nni_group.specification_id) + end + + test "define nni_group" do + {:ok, nni_group} = Nbn.build_nni_group(%{}) + + updates = [ + nni_group: [name: "SYD-POI-01", location: "Sydney Olympic Park"] + ] + + {:ok, nni_group} = + Nbn.define_nni_group(nni_group, %{characteristic_value_updates: updates}) + + Characteristics.check_values( + [nni_group: [name: "SYD-POI-01", location: "Sydney Olympic Park"]], + nni_group + ) + end + end + + describe "build nni" do + @tag debug2: true + test "create an nni" do + {:ok, nni} = Nbn.build_nni(%{}) + + assert is_struct(nni, Nni) + refute is_nil(nni.specification_id) + end + + test "define nni" do + {:ok, nni} = Nbn.build_nni(%{}) + + updates = [ + nni: [port_id: "SYD-01-ETH-001", capacity: 10, technology: :Ethernet] + ] + + {:ok, nni} = Nbn.define_nni(nni, %{characteristic_value_updates: updates}) + + Characteristics.check_values( + [nni: [port_id: "SYD-01-ETH-001", capacity: 10, technology: :Ethernet]], + nni + ) + end + end +end From d51421e3ff69b629d8a5f3c0de250ef1635e30fc Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Tue, 24 Mar 2026 13:02:43 +1030 Subject: [PATCH 02/13] improved identifiers --- lib/access/util.ex | 6 +- lib/nbn/nbn.ex | 1 + lib/nbn/resources/avc.ex | 8 ++- .../{nbn_ethernet_value.ex => pri_value.ex} | 16 +++-- lib/nbn/resources/cvc.ex | 16 ++++- lib/nbn/resources/nbn_ethernet.ex | 63 ++++++++++++++--- lib/nbn/resources/nni.ex | 8 ++- lib/nbn/resources/nni_group.ex | 2 +- lib/nbn/resources/ntd.ex | 8 ++- lib/nbn/resources/uni.ex | 14 ++-- lib/nbn/util.ex | 41 +++++++++++ test/access/shelf_test.exs | 1 - test/diffo_example_test.exs | 10 +++ test/nbn/nbn_ethernet_test.exs | 69 ++++++++++++------- 14 files changed, 214 insertions(+), 49 deletions(-) rename lib/nbn/resources/characteristic_values/{nbn_ethernet_value.ex => pri_value.ex} (55%) create mode 100644 lib/nbn/util.ex create mode 100644 test/diffo_example_test.exs diff --git a/lib/access/util.ex b/lib/access/util.ex index 4f21dee..1b92c74 100644 --- a/lib/access/util.ex +++ b/lib/access/util.ex @@ -13,9 +13,13 @@ defmodule DiffoExample.Access.Util do alias Diffo.Provider.Assignment - def assignments(instance, type) do + @doc """ + Lists things that are assigned_to an Instance, as Assignments + """ + def assignments(instance, type) when is_struct(instance, Ash.Resource) and is_atom(type) do Enum.reduce(instance.reverse_relationships, [], fn reverse_relationship, acc -> IO.inspect(reverse_relationship, label: :reverse_relationship) + case reverse_relationship.type do :assignedTo -> characteristic = diff --git a/lib/nbn/nbn.ex b/lib/nbn/nbn.ex index 8290ac6..0663e89 100644 --- a/lib/nbn/nbn.ex +++ b/lib/nbn/nbn.ex @@ -33,6 +33,7 @@ defmodule DiffoExample.Nbn do define :build_nbn_ethernet, action: :build define :define_nbn_ethernet, action: :define define :relate_nbn_ethernet, action: :relate + define :mine_nbn_ethernet, action: :mine end resource Uni do diff --git a/lib/nbn/resources/avc.ex b/lib/nbn/resources/avc.ex index 8213d4b..e28923f 100644 --- a/lib/nbn/resources/avc.ex +++ b/lib/nbn/resources/avc.ex @@ -43,7 +43,7 @@ defmodule DiffoExample.Nbn.Avc do actions do create :build do description "creates a new AVC resource instance" - accept [:id, :name, :type, :which] + accept [:id, :which] argument :specified_by, :uuid, public?: false argument :relationships, {:array, :struct} argument :features, {:array, :uuid}, public?: false @@ -51,6 +51,8 @@ defmodule DiffoExample.Nbn.Avc do argument :places, {:array, :struct} argument :parties, {:array, :struct} + change set_attribute(:name, &DiffoExample.Nbn.Avc.identifier/0) + change set_attribute(:type, :resource) change before_action(fn changeset, _context -> ActionHelper.build_before(changeset) end) @@ -85,4 +87,8 @@ defmodule DiffoExample.Nbn.Avc do end) end end + + def identifier() do + DiffoExample.Nbn.Util.identifier("AVC") + end end diff --git a/lib/nbn/resources/characteristic_values/nbn_ethernet_value.ex b/lib/nbn/resources/characteristic_values/pri_value.ex similarity index 55% rename from lib/nbn/resources/characteristic_values/nbn_ethernet_value.ex rename to lib/nbn/resources/characteristic_values/pri_value.ex index 57e0d27..2a42e56 100644 --- a/lib/nbn/resources/characteristic_values/nbn_ethernet_value.ex +++ b/lib/nbn/resources/characteristic_values/pri_value.ex @@ -2,16 +2,18 @@ # # SPDX-License-Identifier: MIT -defmodule DiffoExample.Nbn.NbnEthernetValue do +defmodule DiffoExample.Nbn.PriValue do @moduledoc """ Diffo - TMF Service and Resource Management with a difference - NbnEthernetValue - AshTyped Struct for NBN Ethernet Circuit Characteristic Value + NbnEthernetValue - AshTyped Struct for NBN Ethernet Access Characteristic Value """ use Ash.TypedStruct, extensions: [AshJason.TypedStruct, AshOutstanding.TypedStruct] + @technologies [:FTTP, :FTTN, :FTTB, :FTTC, :HFC, :FixedWireless, :Satellite] + jason do - pick [:circuit_id, :speed, :technology] + pick [:avcid, :uniid, :speed, :technology] compact(true) end @@ -20,12 +22,16 @@ defmodule DiffoExample.Nbn.NbnEthernetValue do end typed_struct do - field :circuit_id, :string, description: "the unique NBN circuit identifier" + field :avcid, :string, description: "the avcid from the owne Avc Resource" + + field :uniid, :string, description: "the uniid from the owned Uni Resource" field :speed, :integer, description: "the circuit download speed in Mbps" field :technology, :atom, - description: "the access technology (:FTTP, :FTTN, :HFC, :Fixed_Wireless)" + description: "the access technology", + constraints: [one_of: @technologies], + default: :FTTP end defimpl String.Chars do diff --git a/lib/nbn/resources/cvc.ex b/lib/nbn/resources/cvc.ex index ef17ccd..f5bcee6 100644 --- a/lib/nbn/resources/cvc.ex +++ b/lib/nbn/resources/cvc.ex @@ -34,7 +34,9 @@ defmodule DiffoExample.Nbn.Cvc do id "d4e5f6a7-8b9c-4d0e-bf1a-3b4c5d6e7f8a" name "cvc" type :resourceSpecification + description "A Connectivity Virtual Circuit Resource Instance that aggregates AVCs and terminates at an NNI Group" + category "Network Resource" end @@ -46,7 +48,7 @@ defmodule DiffoExample.Nbn.Cvc do actions do create :build do description "creates a new CVC resource instance" - accept [:id, :name, :type, :which] + accept [:id, :which] argument :specified_by, :uuid, public?: false argument :relationships, {:array, :struct} argument :features, {:array, :uuid}, public?: false @@ -54,6 +56,8 @@ defmodule DiffoExample.Nbn.Cvc do argument :places, {:array, :struct} argument :parties, {:array, :struct} + change set_attribute(:name, &DiffoExample.Nbn.Cvc.identifier/0) + change set_attribute(:type, :resource) change before_action(fn changeset, _context -> ActionHelper.build_before(changeset) end) @@ -99,4 +103,14 @@ defmodule DiffoExample.Nbn.Cvc do end) end end + + attributes do + attribute :cvcid, :string do + default &DiffoExample.Nbn.Cvc.identifier/0 + end + end + + def identifier() do + DiffoExample.Nbn.Util.identifier("CVC") + end end diff --git a/lib/nbn/resources/nbn_ethernet.ex b/lib/nbn/resources/nbn_ethernet.ex index aee6dbf..44a82f4 100644 --- a/lib/nbn/resources/nbn_ethernet.ex +++ b/lib/nbn/resources/nbn_ethernet.ex @@ -6,10 +6,10 @@ defmodule DiffoExample.Nbn.NbnEthernet do @moduledoc """ Diffo - TMF Service and Resource Management with a difference - NbnEthernet - NBN Ethernet Circuit Resource Instance + NbnEthernet - NBN Ethernet access Resource Instance - An NBN Ethernet circuit comprising a dedicated UNI and AVC resource. - The circuit is related to its UNI, which in turn is aggregated by a CVC + An NBN Ethernet access comprising a dedicated UNI and AVC resource. + The access is related to its UNI, which in turn is aggregated by a CVC that terminates at an NNI Group. """ @@ -25,7 +25,7 @@ defmodule DiffoExample.Nbn.NbnEthernet do domain: Nbn resource do - description "An Ash Resource representing an NBN Ethernet Circuit" + description "An Ash Resource representing an NBN Ethernet access" plural_name :NbnEthernets end @@ -33,18 +33,22 @@ defmodule DiffoExample.Nbn.NbnEthernet do id "f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c" name "nbnEthernet" type :resourceSpecification - description "An NBN Ethernet Circuit comprising a dedicated UNI and AVC" + description "An NBN Ethernet access comprising a dedicated UNI and AVC" category "Network Resource" end characteristics do - characteristic :nbn_ethernet, DiffoExample.Nbn.NbnEthernetValue + characteristic :pri, DiffoExample.Nbn.PriValue + # values do + # value :uniid, DiffoExample.Nbn.Uni, :owns, :name + # value :avcid, DiffoExample.Nbn.Avc, :owns, :name + # end end actions do create :build do - description "creates a new NBN Ethernet circuit resource instance" - accept [:id, :name, :type, :which] + description "creates a new NBN Ethernet access resource instance" + accept [:id, :which] argument :specified_by, :uuid, public?: false argument :relationships, {:array, :struct} argument :features, {:array, :uuid}, public?: false @@ -52,6 +56,8 @@ defmodule DiffoExample.Nbn.NbnEthernet do argument :places, {:array, :struct} argument :parties, {:array, :struct} + change set_attribute(:name, &DiffoExample.Nbn.NbnEthernet.identifier/0) + change set_attribute(:type, :resource) change before_action(fn changeset, _context -> ActionHelper.build_before(changeset) end) @@ -65,7 +71,7 @@ defmodule DiffoExample.Nbn.NbnEthernet do end update :define do - description "defines the NBN Ethernet circuit" + description "defines the NBN Ethernet access" argument :characteristic_value_updates, {:array, :term} change after_action(fn changeset, result, _context -> @@ -76,7 +82,7 @@ defmodule DiffoExample.Nbn.NbnEthernet do end update :relate do - description "relates the NBN Ethernet circuit with other instances (e.g. UNI)" + description "relates the NBN Ethernet access with other instances (e.g. UNI)" argument :relationships, {:array, :struct} change after_action(fn changeset, result, _context -> @@ -85,5 +91,42 @@ defmodule DiffoExample.Nbn.NbnEthernet do do: {:ok, result} end) end + + update :mine do + description "updates the NBN Ethernet access with data mined from related instances" + argument :characteristic_value_updates, {:array, :term} + + change before_action(fn changeset, context -> + DiffoExample.Nbn.NbnEthernet.mine_related(changeset, context) + end) + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Characteristic.update_values(result, changeset), + {:ok, result} <- Nbn.get_nbn_ethernet_by_id(result.id), + do: {:ok, result} + end) + end + end + + def identifier() do + DiffoExample.Nbn.Util.identifier("PRI") + end + + # mines related resource to characteristics + def mine_related(changeset, _context) when is_struct(changeset, Ash.Changeset) do + forward_relationships = Ash.Changeset.get_attribute(changeset, :forward_relationships) + + pri_updates = + Enum.into(forward_relationships, [], fn forward_relationship -> + {:ok, related} = Diffo.Provider.get_instance_by_id(forward_relationship.target_id) + {alias_to_id(forward_relationship.alias), related.name} + end) + + Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, pri: pri_updates) + end + + defp alias_to_id(alias) when is_atom(alias) do + (Atom.to_string(alias) <> "id") + |> String.to_atom() end end diff --git a/lib/nbn/resources/nni.ex b/lib/nbn/resources/nni.ex index 81e070a..92785a5 100644 --- a/lib/nbn/resources/nni.ex +++ b/lib/nbn/resources/nni.ex @@ -44,7 +44,7 @@ defmodule DiffoExample.Nbn.Nni do actions do create :build do description "creates a new NNI resource instance" - accept [:id, :name, :type, :which] + accept [:id, :which] argument :specified_by, :uuid, public?: false argument :relationships, {:array, :struct} argument :features, {:array, :uuid}, public?: false @@ -54,6 +54,8 @@ defmodule DiffoExample.Nbn.Nni do change set_attribute(:type, :resource) + change set_attribute(:name, &DiffoExample.Nbn.Nni.identifier/0) + change before_action(fn changeset, _context -> ActionHelper.build_before(changeset) end) change after_action(fn changeset, result, _context -> @@ -85,5 +87,9 @@ defmodule DiffoExample.Nbn.Nni do do: {:ok, result} end) end + + def identifier() do + DiffoExample.Nbn.Util.identifier("NNI") + end end end diff --git a/lib/nbn/resources/nni_group.ex b/lib/nbn/resources/nni_group.ex index 630fdc5..d12080c 100644 --- a/lib/nbn/resources/nni_group.ex +++ b/lib/nbn/resources/nni_group.ex @@ -46,7 +46,7 @@ defmodule DiffoExample.Nbn.NniGroup do actions do create :build do description "creates a new NNI Group resource instance" - accept [:id, :name, :type, :which] + accept [:id, :name, :which] argument :specified_by, :uuid, public?: false argument :relationships, {:array, :struct} argument :features, {:array, :uuid}, public?: false diff --git a/lib/nbn/resources/ntd.ex b/lib/nbn/resources/ntd.ex index 6ef9617..d2f2036 100644 --- a/lib/nbn/resources/ntd.ex +++ b/lib/nbn/resources/ntd.ex @@ -43,7 +43,7 @@ defmodule DiffoExample.Nbn.Ntd do actions do create :build do description "creates a new NTD resource instance" - accept [:id, :name, :type, :which] + accept [:id, :which] argument :specified_by, :uuid, public?: false argument :relationships, {:array, :struct} argument :features, {:array, :uuid}, public?: false @@ -53,6 +53,8 @@ defmodule DiffoExample.Nbn.Ntd do change set_attribute(:type, :resource) + change set_attribute(:name, &DiffoExample.Nbn.Ntd.identifier/0) + change before_action(fn changeset, _context -> ActionHelper.build_before(changeset) end) change after_action(fn changeset, result, _context -> @@ -85,4 +87,8 @@ defmodule DiffoExample.Nbn.Ntd do end) end end + + def identifier() do + DiffoExample.Nbn.Util.identifier("NTD") + end end diff --git a/lib/nbn/resources/uni.ex b/lib/nbn/resources/uni.ex index 2408411..b9bf9dd 100644 --- a/lib/nbn/resources/uni.ex +++ b/lib/nbn/resources/uni.ex @@ -9,7 +9,7 @@ defmodule DiffoExample.Nbn.Uni do Uni - User Network Interface Resource Instance A UNI is the physical/logical interface at the customer premises. It is - related to an NTD resource and to its parent NBN Ethernet circuit. + related to an NTD resource and to its parent NBN Ethernet access. It is related to an AVC resource, which is in turn aggregated by a CVC. """ @@ -33,7 +33,7 @@ defmodule DiffoExample.Nbn.Uni do id "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d" name "uni" type :resourceSpecification - description "A UNI Resource Instance related to an NTD and an NBN Ethernet circuit" + description "A UNI Resource Instance related to an NTD and an NBN Ethernet access" category "Network Resource" end @@ -44,7 +44,7 @@ defmodule DiffoExample.Nbn.Uni do actions do create :build do description "creates a new UNI resource instance" - accept [:id, :name, :type, :which] + accept [:id, :which] argument :specified_by, :uuid, public?: false argument :relationships, {:array, :struct} argument :features, {:array, :uuid}, public?: false @@ -54,6 +54,8 @@ defmodule DiffoExample.Nbn.Uni do change set_attribute(:type, :resource) + change set_attribute(:name, &DiffoExample.Nbn.Uni.identifier/0) + change before_action(fn changeset, _context -> ActionHelper.build_before(changeset) end) change after_action(fn changeset, result, _context -> @@ -76,7 +78,7 @@ defmodule DiffoExample.Nbn.Uni do end update :relate do - description "relates the UNI with other instances (e.g. NTD, NBN Ethernet circuit)" + description "relates the UNI with other instances (e.g. NTD, NBN Ethernet access)" argument :relationships, {:array, :struct} change after_action(fn changeset, result, _context -> @@ -86,4 +88,8 @@ defmodule DiffoExample.Nbn.Uni do end) end end + + def identifier() do + DiffoExample.Nbn.Util.identifier("UNI") + end end diff --git a/lib/nbn/util.ex b/lib/nbn/util.ex new file mode 100644 index 0000000..ea68e1d --- /dev/null +++ b/lib/nbn/util.ex @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.Util do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + Util - various utilities for NBN domain + + """ + + @doc """ + Generates a new random NBN identifier with the prefix + + ## Examples + iex> identifier = DiffoExample.Nbn.Util.identifier("AVC") + iex> DiffoExample.Nbn.Util.identifier?(identifier) + true + + """ + def identifier(prefix) when is_binary(prefix) and byte_size(prefix) == 3 do + prefix <> + (:rand.uniform(000_999_999_999) + |> Integer.to_string() + |> String.pad_leading(12, "0")) + end + + @doc """ + Returns whether the identifier is a valid NBN identifier + + ## Examples + iex> DiffoExample.Nbn.Util.identifier?("AVC120123456789") + true + iex> DiffoExample.Nbn.Util.identifier?("avc120123456789") + false + """ + def identifier?(identifier) when is_binary(identifier) do + Regex.match?(~r/[A-Z]{3}\d{12}/, identifier) + end +end diff --git a/test/access/shelf_test.exs b/test/access/shelf_test.exs index f49f692..f3bc30e 100644 --- a/test/access/shelf_test.exs +++ b/test/access/shelf_test.exs @@ -122,7 +122,6 @@ defmodule DiffoExample.Access.ShelfTest do ~s({\"id\":\"#{shelf.id}",\"href\":\"resourceInventoryManagement/v4/resource/shelf/#{shelf.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"name\":\"shelf\",\"version\":\"v1.0.0\"},\"resourceRelationship\":[{\"type\":\"contains\",\"resource\":{\"id\":\"#{card0.id}\",\"href\":\"resourceInventoryManagement/v4/resource/card/#{card0.id}\"}},{\"type\":\"contains\",\"resource\":{\"id\":\"#{card1.id}\",\"href\":\"resourceInventoryManagement/v4/resource/card/#{card1.id}\"}},{\"type\":\"contains\",\"resource\":{\"id\":\"#{card2.id}\",\"href\":\"resourceInventoryManagement/v4/resource/card/#{card2.id}\"}},{\"type\":\"contains\",\"resource\":{\"id\":\"#{card3.id}\",\"href\":\"resourceInventoryManagement/v4/resource/card/#{card3.id}\"}}],\"resourceCharacteristic\":[{\"name\":\"shelf\",\"value\":{\"name\":\"QDONC-1001\",\"family\":\"ISAM\",\"model\":\"ISAM7330\",\"technology\":\"DSLAM\"}},{\"name\":\"slots\",\"value\":{\"first\":1,\"last\":10,\"free\":10,\"type\":\"LineCard\",\"algorithm\":\"lowest\"}}],\"place\":[{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) end - @tag debug: true test "auto assign line cards" do places = [create_esa_place()] parties = [create_provider_party()] diff --git a/test/diffo_example_test.exs b/test/diffo_example_test.exs new file mode 100644 index 0000000..d500bb4 --- /dev/null +++ b/test/diffo_example_test.exs @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExampleTest do + @moduledoc false + use ExUnit.Case + doctest DiffoExample.Access.Util + doctest DiffoExample.Nbn.Util +end diff --git a/test/nbn/nbn_ethernet_test.exs b/test/nbn/nbn_ethernet_test.exs index 5b3e0d0..8f3a24a 100644 --- a/test/nbn/nbn_ethernet_test.exs +++ b/test/nbn/nbn_ethernet_test.exs @@ -16,47 +16,49 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do alias DiffoExample.Nbn.NniGroup alias DiffoExample.Nbn.Nni alias DiffoExample.Test.Characteristics + alias Diffo.Provider.Instance.Relationship setup_all do AshNeo4j.BoltyHelper.start() end setup do - on_exit(fn -> - AshNeo4j.Neo4jHelper.delete_all() - end) + # on_exit(fn -> + # AshNeo4j.Neo4jHelper.delete_all() + # end) + :ok end describe "build nbn_ethernet" do - test "create an nbn_ethernet circuit" do - {:ok, circuit} = Nbn.build_nbn_ethernet(%{}) + test "create an nbn_ethernet access" do + {:ok, access} = Nbn.build_nbn_ethernet(%{}) # check the instance is an NbnEthernet - assert is_struct(circuit, NbnEthernet) + assert is_struct(access, NbnEthernet) # check specification resource enrichment and node relationship - refute is_nil(circuit.specification_id) - assert is_struct(circuit.specification, Specification) + refute is_nil(access.specification_id) + assert is_struct(access.specification, Specification) assert AshNeo4j.Neo4jHelper.nodes_relate_how?( :Instance, - %{uuid: circuit.id}, + %{uuid: access.id}, :Specification, - %{uuid: circuit.specification_id}, + %{uuid: access.specification_id}, :SPECIFIED_BY, :outgoing ) # check characteristic resource enrichment and node relationships - assert is_list(circuit.characteristics) - assert length(circuit.characteristics) == 1 + assert is_list(access.characteristics) + assert length(access.characteristics) == 1 - Enum.each(circuit.characteristics, fn characteristic -> + Enum.each(access.characteristics, fn characteristic -> assert is_struct(characteristic, Characteristic) assert AshNeo4j.Neo4jHelper.nodes_relate_how?( :Instance, - %{uuid: circuit.id}, + %{uuid: access.id}, :Characteristic, %{uuid: characteristic.id}, :HAS, @@ -64,26 +66,48 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do ) end) - encoding = Jason.encode!(circuit) |> Diffo.Util.summarise_dates() + encoding = Jason.encode!(access) |> Diffo.Util.summarise_dates() assert encoding == - ~s({"id":"#{circuit.id}","href":"resourceInventoryManagement/v4/resource/nbnEthernet/#{circuit.id}","category":"Network Resource","resourceSpecification":{"id":"f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","href":"resourceCatalogManagement/v4/resourceSpecification/f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","name":"nbnEthernet","version":"v1.0.0"},"resourceCharacteristic":[{"name":"nbn_ethernet","value":{}}]}) + ~s({"id":"#{access.id}","href":"resourceInventoryManagement/v4/resource/nbnEthernet/#{access.id}","category":"Network Resource",\"name\":\"#{access.name}","resourceSpecification":{"id":"f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","href":"resourceCatalogManagement/v4/resourceSpecification/f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","name":"nbnEthernet","version":"v1.0.0"},"resourceCharacteristic":[{"name":"pri","value":{\"technology\":\"FTTP\"}}]}) end - test "define nbn_ethernet circuit" do - {:ok, circuit} = Nbn.build_nbn_ethernet(%{}) + test "define nbn_ethernet access" do + {:ok, access} = Nbn.build_nbn_ethernet(%{}) updates = [ - nbn_ethernet: [circuit_id: "NBN-CID-123456", speed: 1000, technology: :FTTP] + pri: [avcid: "AVC000910202941", uniid: "UNI000302814545", speed: 1000, technology: :FTTP] ] - {:ok, circuit} = Nbn.define_nbn_ethernet(circuit, %{characteristic_value_updates: updates}) + {:ok, access} = Nbn.define_nbn_ethernet(access, %{characteristic_value_updates: updates}) Characteristics.check_values( - [nbn_ethernet: [circuit_id: "NBN-CID-123456", speed: 1000, technology: :FTTP]], - circuit + [pri: [avcid: "AVC000910202941", uniid: "UNI000302814545", speed: 1000, technology: :FTTP]], + access ) end + + test "relate nbn_ethernet" do + {:ok, access} = Nbn.build_nbn_ethernet(%{}) + + {:ok, avc} = Nbn.build_avc(%{}) + + {:ok, uni} = Nbn.build_uni(%{}) + + relationships = [ + %Relationship{id: avc.id, direction: :forward, type: :owns, alias: :avc}, + %Relationship{id: uni.id, direction: :forward, type: :owns, alias: :uni} + ] + + {:ok, access} = Nbn.relate_nbn_ethernet(access, %{relationships: relationships}) + + {:ok, access} = Nbn.mine_nbn_ethernet(access) + + encoding = Jason.encode!(access) |> Diffo.Util.summarise_dates() + + assert encoding == + ~s({"id":"#{access.id}","href":"resourceInventoryManagement/v4/resource/nbnEthernet/#{access.id}","category":"Network Resource","name":"#{access.name}","resourceSpecification":{"id":"f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","href":"resourceCatalogManagement/v4/resourceSpecification/f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","name":"nbnEthernet","version":"v1.0.0"},"resourceRelationship":[{"alias":"avc","type":"owns","resource":{"id":"#{avc.id}","href":"resourceInventoryManagement/v4/resource/avc/#{avc.id}"}},{"alias":"uni","type":"owns","resource":{"id\":"#{uni.id}","href":"resourceInventoryManagement/v4/resource/uni/#{uni.id}"}}],"supportingResource":[{"id":"avc","href":"resourceInventoryManagement/v4/resource/avc/#{avc.id}"},{"id\":"uni","href":"resourceInventoryManagement/v4/resource/uni/#{uni.id}"}],"resourceCharacteristic":[{"name":"pri","value":{"avcid":"#{avc.name}","uniid":"#{uni.name}","technology":"FTTP"}}]}) + end end describe "build uni" do @@ -214,7 +238,6 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do end describe "build nni" do - @tag debug2: true test "create an nni" do {:ok, nni} = Nbn.build_nni(%{}) From 87680027be1a307ec2994c8671767016e73354f2 Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Thu, 26 Mar 2026 23:55:04 +1030 Subject: [PATCH 03/13] derive speeds from ntd technology and avc bandwidth_profile --- lib/access/util.ex | 2 - lib/nbn/nbn.ex | 4 + lib/nbn/resources/avc.ex | 25 +++ .../characteristic_values/avc_value.ex | 10 +- .../characteristic_values/cvc_value.ex | 6 +- .../characteristic_values/ntd_value.ex | 5 +- .../characteristic_values/pri_value.ex | 19 +- .../characteristic_values/uni_value.ex | 13 +- lib/nbn/resources/cvc.ex | 34 +++- lib/nbn/resources/nbn_ethernet.ex | 24 ++- lib/nbn/resources/nni_group.ex | 5 +- lib/nbn/resources/ntd.ex | 16 +- lib/nbn/resources/types/bandwidth_profile.ex | 23 +++ lib/nbn/resources/types/speeds.ex | 49 +++++ lib/nbn/resources/types/technology.ex | 21 +++ lib/nbn/resources/uni.ex | 29 +++ lib/nbn/util.ex | 127 ++++++++++++- test/nbn/nbn_ethernet_test.exs | 170 ++++++++++++++++-- test/support/characteristics.ex | 3 + 19 files changed, 522 insertions(+), 63 deletions(-) create mode 100644 lib/nbn/resources/types/bandwidth_profile.ex create mode 100644 lib/nbn/resources/types/speeds.ex create mode 100644 lib/nbn/resources/types/technology.ex diff --git a/lib/access/util.ex b/lib/access/util.ex index 1b92c74..d8285a7 100644 --- a/lib/access/util.ex +++ b/lib/access/util.ex @@ -18,8 +18,6 @@ defmodule DiffoExample.Access.Util do """ def assignments(instance, type) when is_struct(instance, Ash.Resource) and is_atom(type) do Enum.reduce(instance.reverse_relationships, [], fn reverse_relationship, acc -> - IO.inspect(reverse_relationship, label: :reverse_relationship) - case reverse_relationship.type do :assignedTo -> characteristic = diff --git a/lib/nbn/nbn.ex b/lib/nbn/nbn.ex index 0663e89..3ae584e 100644 --- a/lib/nbn/nbn.ex +++ b/lib/nbn/nbn.ex @@ -41,6 +41,7 @@ defmodule DiffoExample.Nbn do define :build_uni, action: :build define :define_uni, action: :define define :relate_uni, action: :relate + define :mine_uni, action: :mine end resource Avc do @@ -48,12 +49,14 @@ defmodule DiffoExample.Nbn do define :build_avc, action: :build define :define_avc, action: :define define :relate_avc, action: :relate + define :mine_avc, action: :mine end resource Ntd do define :get_ntd_by_id, action: :read, get_by: :id define :build_ntd, action: :build define :define_ntd, action: :define + define :assign_port, action: :assign_port define :relate_ntd, action: :relate end @@ -63,6 +66,7 @@ defmodule DiffoExample.Nbn do define :define_cvc, action: :define define :assign_cvlan, action: :assign_cvlan define :relate_cvc, action: :relate + define :mine_cvc, action: :mine end resource NniGroup do diff --git a/lib/nbn/resources/avc.ex b/lib/nbn/resources/avc.ex index e28923f..5f36f8a 100644 --- a/lib/nbn/resources/avc.ex +++ b/lib/nbn/resources/avc.ex @@ -38,6 +38,7 @@ defmodule DiffoExample.Nbn.Avc do characteristics do characteristic :avc, DiffoExample.Nbn.AvcValue + characteristic :cvc, DiffoExample.Nbn.CvcValue end actions do @@ -86,9 +87,33 @@ defmodule DiffoExample.Nbn.Avc do do: {:ok, result} end) end + + update :mine do + description "updates the AVC with data mined from related instances" + argument :characteristic_value_updates, {:array, :term} + + change before_action(fn changeset, context -> + DiffoExample.Nbn.Avc.mine_related(changeset, context) + end) + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Characteristic.update_values(result, changeset), + {:ok, result} <- Nbn.get_avc_by_id(result.id), + do: {:ok, result} + end) + end end def identifier() do DiffoExample.Nbn.Util.identifier("AVC") end + + # mines related resource to characteristics + def mine_related(changeset, _context) when is_struct(changeset, Ash.Changeset) do + reverse_relationships = Ash.Changeset.get_attribute(changeset, :reverse_relationships) + + cvlan = {:cvlan, hd(hd(reverse_relationships).characteristics).value} + + Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, avc: [cvlan]) + end end diff --git a/lib/nbn/resources/characteristic_values/avc_value.ex b/lib/nbn/resources/characteristic_values/avc_value.ex index f24266a..201d484 100644 --- a/lib/nbn/resources/characteristic_values/avc_value.ex +++ b/lib/nbn/resources/characteristic_values/avc_value.ex @@ -10,19 +10,21 @@ defmodule DiffoExample.Nbn.AvcValue do """ use Ash.TypedStruct, extensions: [AshJason.TypedStruct, AshOutstanding.TypedStruct] + alias DiffoExample.Nbn.BandwidthProfile + jason do - pick [:cir, :pir] + pick [:cvlan, :bandwidth_profile] compact(true) end outstanding do - expect [:cir, :pir] + expect [:cvlan, :bandwidth_profile] end typed_struct do - field :cir, :integer, description: "Committed Information Rate in Mbps" + field :cvlan, :string, description: "the cvlan of the AVC, assigned by the related CVC" - field :pir, :integer, description: "Peak Information Rate in Mbps" + field :bandwidth_profile, BandwidthProfile, description: "the bandwidth profile of the AVC" end defimpl String.Chars do diff --git a/lib/nbn/resources/characteristic_values/cvc_value.ex b/lib/nbn/resources/characteristic_values/cvc_value.ex index dc68a2e..e13e3d6 100644 --- a/lib/nbn/resources/characteristic_values/cvc_value.ex +++ b/lib/nbn/resources/characteristic_values/cvc_value.ex @@ -11,16 +11,16 @@ defmodule DiffoExample.Nbn.CvcValue do use Ash.TypedStruct, extensions: [AshJason.TypedStruct, AshOutstanding.TypedStruct] jason do - pick [:cvc_id, :bandwidth] + pick [:svlan, :bandwidth] compact(true) end outstanding do - expect [:cvc_id, :bandwidth] + expect [:svlan, :bandwidth] end typed_struct do - field :cvc_id, :string, description: "the unique CVC identifier" + field :svlan, :string, description: "the svlan of the CVC, assigned by the related NNI Group" field :bandwidth, :integer, description: "total CVC bandwidth in Mbps" end diff --git a/lib/nbn/resources/characteristic_values/ntd_value.ex b/lib/nbn/resources/characteristic_values/ntd_value.ex index 85ee77b..fa591a0 100644 --- a/lib/nbn/resources/characteristic_values/ntd_value.ex +++ b/lib/nbn/resources/characteristic_values/ntd_value.ex @@ -10,6 +10,8 @@ defmodule DiffoExample.Nbn.NtdValue do """ use Ash.TypedStruct, extensions: [AshJason.TypedStruct, AshOutstanding.TypedStruct] + alias DiffoExample.Nbn.Technology + jason do pick [:model, :serial_number, :technology] compact(true) @@ -24,8 +26,7 @@ defmodule DiffoExample.Nbn.NtdValue do field :serial_number, :string, description: "the NTD serial number" - field :technology, :atom, - description: "the access technology (:FTTP, :FTTN, :HFC, :Fixed_Wireless)" + field :technology, Technology, description: "the access technology type", default: Technology.default end defimpl String.Chars do diff --git a/lib/nbn/resources/characteristic_values/pri_value.ex b/lib/nbn/resources/characteristic_values/pri_value.ex index 2a42e56..efe9a09 100644 --- a/lib/nbn/resources/characteristic_values/pri_value.ex +++ b/lib/nbn/resources/characteristic_values/pri_value.ex @@ -10,28 +10,29 @@ defmodule DiffoExample.Nbn.PriValue do """ use Ash.TypedStruct, extensions: [AshJason.TypedStruct, AshOutstanding.TypedStruct] - @technologies [:FTTP, :FTTN, :FTTB, :FTTC, :HFC, :FixedWireless, :Satellite] + alias DiffoExample.Nbn.Technology + alias DiffoExample.Nbn.BandwidthProfile + alias DiffoExample.Nbn.Speeds jason do - pick [:avcid, :uniid, :speed, :technology] + pick [:avcid, :uniid, :technology, :bandwidth_profile, :speeds] compact(true) end outstanding do - expect [:circuit_id, :speed] + expect [:avcid, :uniid, :technology, :bandwidth_profile, :speeds] end typed_struct do - field :avcid, :string, description: "the avcid from the owne Avc Resource" + field :avcid, :string, description: "the avcid from the owned Avc Resource" field :uniid, :string, description: "the uniid from the owned Uni Resource" - field :speed, :integer, description: "the circuit download speed in Mbps" + field :technology, Technology, description: "the technology type" - field :technology, :atom, - description: "the access technology", - constraints: [one_of: @technologies], - default: :FTTP + field :bandwidth_profile, BandwidthProfile, description: "the bandwidth profile" + + field :speeds, Speeds, description: "the downstream and upstream speeds in Mbps" end defimpl String.Chars do diff --git a/lib/nbn/resources/characteristic_values/uni_value.ex b/lib/nbn/resources/characteristic_values/uni_value.ex index 56e8870..8bd5670 100644 --- a/lib/nbn/resources/characteristic_values/uni_value.ex +++ b/lib/nbn/resources/characteristic_values/uni_value.ex @@ -10,22 +10,23 @@ defmodule DiffoExample.Nbn.UniValue do """ use Ash.TypedStruct, extensions: [AshJason.TypedStruct, AshOutstanding.TypedStruct] + alias DiffoExample.Nbn.Technology + jason do - pick [:vlan_id, :bandwidth_profile, :technology] + pick [:port, :encapsulation, :technology] compact(true) end outstanding do - expect [:vlan_id, :technology] + expect [:port, :encapsulation, :technology] end typed_struct do - field :vlan_id, :integer, description: "the VLAN ID for the UNI" + field :port, :integer, description: "the port of the UNI, assigned by the related NTD" - field :bandwidth_profile, :string, description: "the bandwidth profile name for the UNI" + field :encapsulation, :string, description: "the encapsulation of the UNI" - field :technology, :atom, - description: "the access technology (:FTTP, :FTTN, :HFC, :Fixed_Wireless)" + field :technology, Technology, description: "the access technology type" end defimpl String.Chars do diff --git a/lib/nbn/resources/cvc.ex b/lib/nbn/resources/cvc.ex index f5bcee6..9b21921 100644 --- a/lib/nbn/resources/cvc.ex +++ b/lib/nbn/resources/cvc.ex @@ -8,8 +8,8 @@ defmodule DiffoExample.Nbn.Cvc do Cvc - Connectivity Virtual Circuit Resource Instance - A CVC is the wholesale bandwidth product that aggregates one or more AVC - resources and terminates at an NNI Group resource. Each AVC has a related UNI. + A CVC is the wholesale bandwidth product that supports AVC and terminates at an NNI Group. + The CVC assigns cvlan to AVC. """ alias Diffo.Provider.BaseInstance @@ -42,7 +42,7 @@ defmodule DiffoExample.Nbn.Cvc do characteristics do characteristic :cvc, DiffoExample.Nbn.CvcValue - characteristic :cvlan_ids, Diffo.Provider.AssignableValue + characteristic :cvlans, Diffo.Provider.AssignableValue end actions do @@ -86,7 +86,7 @@ defmodule DiffoExample.Nbn.Cvc do argument :assignment, :struct, constraints: [instance_of: Assignment] change after_action(fn changeset, result, _context -> - with {:ok, result} <- Assigner.assign(result, changeset, :cvlan_ids, :cvlan_id), + with {:ok, result} <- Assigner.assign(result, changeset, :cvlans, :cvlan), {:ok, result} <- Nbn.get_cvc_by_id(result.id), do: {:ok, result} end) @@ -102,15 +102,33 @@ defmodule DiffoExample.Nbn.Cvc do do: {:ok, result} end) end - end - attributes do - attribute :cvcid, :string do - default &DiffoExample.Nbn.Cvc.identifier/0 + update :mine do + description "updates the CVC with data mined from related instances" + argument :characteristic_value_updates, {:array, :term} + + change before_action(fn changeset, context -> + DiffoExample.Nbn.Cvc.mine_related(changeset, context) + end) + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Characteristic.update_values(result, changeset), + {:ok, result} <- Nbn.get_cvc_by_id(result.id), + do: {:ok, result} + end) end end def identifier() do DiffoExample.Nbn.Util.identifier("CVC") end + + # mines related resource to characteristics + def mine_related(changeset, _context) when is_struct(changeset, Ash.Changeset) do + reverse_relationships = Ash.Changeset.get_attribute(changeset, :reverse_relationships) + + svlan = {:svlan, hd(hd(reverse_relationships).characteristics).value} + + Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, cvc: [svlan]) + end end diff --git a/lib/nbn/resources/nbn_ethernet.ex b/lib/nbn/resources/nbn_ethernet.ex index 44a82f4..ebb0413 100644 --- a/lib/nbn/resources/nbn_ethernet.ex +++ b/lib/nbn/resources/nbn_ethernet.ex @@ -8,9 +8,7 @@ defmodule DiffoExample.Nbn.NbnEthernet do NbnEthernet - NBN Ethernet access Resource Instance - An NBN Ethernet access comprising a dedicated UNI and AVC resource. - The access is related to its UNI, which in turn is aggregated by a CVC - that terminates at an NNI Group. + An NBN Ethernet access comprises of dedicated UNI and AVC resources. """ alias Diffo.Provider.BaseInstance @@ -19,6 +17,7 @@ defmodule DiffoExample.Nbn.NbnEthernet do alias Diffo.Provider.Instance.ActionHelper alias DiffoExample.Nbn + alias DiffoExample.Nbn.Util use Ash.Resource, fragments: [BaseInstance], @@ -117,12 +116,25 @@ defmodule DiffoExample.Nbn.NbnEthernet do forward_relationships = Ash.Changeset.get_attribute(changeset, :forward_relationships) pri_updates = - Enum.into(forward_relationships, [], fn forward_relationship -> + Enum.reduce(forward_relationships, [], fn forward_relationship, acc -> {:ok, related} = Diffo.Provider.get_instance_by_id(forward_relationship.target_id) - {alias_to_id(forward_relationship.alias), related.name} + related_name = {alias_to_id(forward_relationship.alias), related.name} + case forward_relationship.alias do + :uni -> + # extract technology from uni characteristic + [{:technology, Util.extract(related.characteristics, :uni, :technology)} | [ related_name | acc]] + :avc -> + # extract bandwidth_profile from avc characteristic + [{:bandwidth_profile, Util.extract(related.characteristics, :avc, :bandwidth_profile)} | [ related_name | acc]] + _ -> + [ related_name | acc] + end end) - Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, pri: pri_updates) + # calculate the speeds from the extracted technology and bandwidth_profile + speeds = {:speeds, Util.speeds(Keyword.get(pri_updates, :bandwidth_profile), Keyword.get(pri_updates, :technology))} + + Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, pri: [speeds | pri_updates]) end defp alias_to_id(alias) when is_atom(alias) do diff --git a/lib/nbn/resources/nni_group.ex b/lib/nbn/resources/nni_group.ex index d12080c..9629d5b 100644 --- a/lib/nbn/resources/nni_group.ex +++ b/lib/nbn/resources/nni_group.ex @@ -10,6 +10,7 @@ defmodule DiffoExample.Nbn.NniGroup do An NNI Group is the Point of Interconnect (PoI) grouping where a CVC terminates. It comprises multiple NNI resources. + The NNI Group assigns svlan to CVC. """ alias Diffo.Provider.BaseInstance @@ -40,7 +41,7 @@ defmodule DiffoExample.Nbn.NniGroup do characteristics do characteristic :nni_group, DiffoExample.Nbn.NniGroupValue - characteristic :svlan_ids, Diffo.Provider.AssignableValue + characteristic :svlans, Diffo.Provider.AssignableValue end actions do @@ -82,7 +83,7 @@ defmodule DiffoExample.Nbn.NniGroup do argument :assignment, :struct, constraints: [instance_of: Assignment] change after_action(fn changeset, result, _context -> - with {:ok, result} <- Assigner.assign(result, changeset, :svlan_ids, :svlan_id), + with {:ok, result} <- Assigner.assign(result, changeset, :svlans, :svlan), {:ok, result} <- Nbn.get_nni_group_by_id(result.id), do: {:ok, result} end) diff --git a/lib/nbn/resources/ntd.ex b/lib/nbn/resources/ntd.ex index d2f2036..b9fa3ea 100644 --- a/lib/nbn/resources/ntd.ex +++ b/lib/nbn/resources/ntd.ex @@ -9,13 +9,15 @@ defmodule DiffoExample.Nbn.Ntd do Ntd - Network Termination Device Resource Instance An NTD is the device installed at the customer premises that connects - the premises to the NBN network. It is related to a UNI resource. + the premises to the NBN network. The NTD can assign ports to UNI. """ alias Diffo.Provider.BaseInstance alias Diffo.Provider.Instance.Relationship alias Diffo.Provider.Instance.Characteristic alias Diffo.Provider.Instance.ActionHelper + alias Diffo.Provider.Assigner + alias Diffo.Provider.Assignment alias DiffoExample.Nbn @@ -38,6 +40,7 @@ defmodule DiffoExample.Nbn.Ntd do characteristics do characteristic :ntd, DiffoExample.Nbn.NtdValue + characteristic :ports, Diffo.Provider.AssignableValue end actions do @@ -76,6 +79,17 @@ defmodule DiffoExample.Nbn.Ntd do end) end + update :assign_port do + description "assigns a port from the NTD pool to a UNI" + argument :assignment, :struct, constraints: [instance_of: Assignment] + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Assigner.assign(result, changeset, :ports, :port), + {:ok, result} <- Nbn.get_ntd_by_id(result.id), + do: {:ok, result} + end) + end + update :relate do description "relates the NTD with other instances (e.g. UNI)" argument :relationships, {:array, :struct} diff --git a/lib/nbn/resources/types/bandwidth_profile.ex b/lib/nbn/resources/types/bandwidth_profile.ex new file mode 100644 index 0000000..fdc6848 --- /dev/null +++ b/lib/nbn/resources/types/bandwidth_profile.ex @@ -0,0 +1,23 @@ +defmodule DiffoExample.Nbn.BandwidthProfile do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + BandwidthProfile type for NBN domain + """ + + require Ash.Type.NewType + + use Ash.Type.NewType, + subtype_of: :atom, + constraints: [one_of: bandwidth_profiles()] + + def default do + :home_fast + end + + def bandwidth_profiles do + [:D12_U1, :D25_U5, :D25_U10, :D50_U20, :D100_U40, :D250_U100, :D500_U200, :D1000_U400, + :wireless_plus, :wireless_fast, :wireless_superfast, + :home_fast, :home_superfast, :home_ultrafast, :home_hyperfast] + end +end diff --git a/lib/nbn/resources/types/speeds.ex b/lib/nbn/resources/types/speeds.ex new file mode 100644 index 0000000..36fea9c --- /dev/null +++ b/lib/nbn/resources/types/speeds.ex @@ -0,0 +1,49 @@ +defmodule DiffoExample.Nbn.Speeds do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + Speeds type for NBN domain + """ + + require Ash.Type.NewType + + use Ash.Type.NewType, + subtype_of: :tuple, + constraints: [ + fields: [downstream: [type: :integer], upstream: [type: :integer]] + ] + + def speeds do + [ + {12, 1}, {25, 5}, {25, 10}, {50, 20}, {100, 40}, {250, 100}, {500, 200}, {1000, 400}, + # :home_fast + {500, 50}, + # :home_superfast + {750, 50}, + # :home_ultrafast + {1000, 100}, + # :home_hyperfast + {2000, 100}, {2000, 200}, + # :wireless_plus, :wireless_fast, :wireless_superfast + {100, 20}, {250, 20}, {400, 40} + ] + end + + @impl true + def cast_input(value, _constraints) when is_tuple(value) do + if value in speeds() do + {:ok, value} + else + {:error, "invalid downstream and upstream speed combination"} + end + end + + def cast_input({_value, _constraints}), do: {:error, "value must be a tuple"} + + defimpl Jason.Encoder do + def encode(speeds, _opts) do + Jason.OrderedObject.new(downstream: speeds.downstream, upstream: speeds.upstream, units: "Mbps") + |> Jason.encode!() + end + end +end diff --git a/lib/nbn/resources/types/technology.ex b/lib/nbn/resources/types/technology.ex new file mode 100644 index 0000000..c9d6003 --- /dev/null +++ b/lib/nbn/resources/types/technology.ex @@ -0,0 +1,21 @@ +defmodule DiffoExample.Nbn.Technology do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + Technology type for NBN domain + """ + + require Ash.Type.NewType + + use Ash.Type.NewType, + subtype_of: :atom, + constraints: [one_of: technology()] + + def default do + :FTTP + end + + def technology do + [:FTTP, :FTTN, :FTTB, :FTTC, :HFC, :FixedWireless, :Satellite] + end +end diff --git a/lib/nbn/resources/uni.ex b/lib/nbn/resources/uni.ex index b9bf9dd..3966828 100644 --- a/lib/nbn/resources/uni.ex +++ b/lib/nbn/resources/uni.ex @@ -19,6 +19,7 @@ defmodule DiffoExample.Nbn.Uni do alias Diffo.Provider.Instance.ActionHelper alias DiffoExample.Nbn + alias DiffoExample.Nbn.Util use Ash.Resource, fragments: [BaseInstance], @@ -87,9 +88,37 @@ defmodule DiffoExample.Nbn.Uni do do: {:ok, result} end) end + + update :mine do + description "updates the UNI with data mined from related instances" + argument :characteristic_value_updates, {:array, :term} + + change before_action(fn changeset, context -> + DiffoExample.Nbn.Uni.mine_related(changeset, context) + end) + + change after_action(fn changeset, result, _context -> + with {:ok, result} <- Characteristic.update_values(result, changeset), + {:ok, result} <- Nbn.get_uni_by_id(result.id), + do: {:ok, result} + end) + end end def identifier() do DiffoExample.Nbn.Util.identifier("UNI") end + + # mines related resource to characteristics + def mine_related(changeset, _context) when is_struct(changeset, Ash.Changeset) do + reverse_relationships = Ash.Changeset.get_attribute(changeset, :reverse_relationships) + + ntd_relationship = hd(reverse_relationships) + + port = {:port, hd(ntd_relationship.characteristics).value} + {:ok, ntd} = Diffo.Provider.get_instance_by_id(ntd_relationship.source_id) + technology = {:technology, Util.extract(ntd.characteristics, :ntd, :technology)} + + Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, uni: [port, technology]) + end end diff --git a/lib/nbn/util.ex b/lib/nbn/util.ex index ea68e1d..345041f 100644 --- a/lib/nbn/util.ex +++ b/lib/nbn/util.ex @@ -7,9 +7,10 @@ defmodule DiffoExample.Nbn.Util do Diffo - TMF Service and Resource Management with a difference Util - various utilities for NBN domain - """ + alias DiffoExample.Nbn.Technology + @doc """ Generates a new random NBN identifier with the prefix @@ -38,4 +39,128 @@ defmodule DiffoExample.Nbn.Util do def identifier?(identifier) when is_binary(identifier) do Regex.match?(~r/[A-Z]{3}\d{12}/, identifier) end + + @doc """ + Extracts a field value from a named item in a list + + ## Examples + iex> DiffoExample.Nbn.Util.extract([%{name: :avc, value: %{cvlan: 1}}], :avc, :cvlan) + 1 + """ + def extract(items, name, field) when is_list(items) and is_atom(name) and is_atom(field) do + Enum.reduce_while(items, nil, fn item, acc -> + if name == item.name do + if item.value != nil do + {:halt, Map.get(item.value, field)} + else + {:halt, nil} + end + else + {:cont, acc} + end + end) + end + + @doc""" + Returns a tuple of maximum downstream and upstream speeds in Mbps + given the bandwidth_profile and technology, or :error + + ## Examples + iex> DiffoExample.Nbn.Util.speeds(:D12_U1, :Satellite) + {12, 1} + iex> DiffoExample.Nbn.Util.speeds(:home_fast, :FTTP) + {500, 50} + iex> DiffoExample.Nbn.Util.speeds(:home_hyperfast, :HFC) + {2000, 100} + iex> DiffoExample.Nbn.Util.speeds(:home_fast, :FixedWireless) + :error + """ + def speeds(:D12_U1, technology) when is_atom(technology) do + if technology in Technology.technology() do + {12, 1} + else + :error + end + end + + def speeds(:D25_U5, technology) when is_atom(technology) do + if technology in Technology.technology() do + {25, 5} + else + :error + end + end + + def speeds(:D25_U10, technology) when is_atom(technology) do + if technology in [:FTTP, :HFC, :FTTC] do + {25, 10} + else + :error + end + end + + def speeds(:D50_U20, technology) when is_atom(technology) do + if technology in [:FTTP, :HFC, :FTTC] do + {50, 20} + else + :error + end + end + + def speeds(bandwidth_profile, :FixedWireless) do + case bandwidth_profile do + :wireless_plus -> + {100, 20} + :wireless_fast -> + {250, 20} + :wireless_superfast -> + {400, 40} + _ -> + :error + end + end + + def speeds(bandwidth_profile, :HFC) do + case bandwidth_profile do + :home_fast -> + {500, 50} + :home_superfast -> + {750, 50} + :home_ultrafast -> + {1000, 100} + :home_hyperfast -> + {2000, 100} + :U100_D40 -> + {100, 40} + _ -> + :error + end + end + + def speeds(bandwidth_profile, :FTTP) do + case bandwidth_profile do + :home_fast -> + {500, 50} + :home_superfast -> + {750, 50} + :home_ultrafast -> + {1000, 100} + :home_hyperfast -> + {2000, 200} + :D100_U40 -> + {100, 40} + :D250_U100 -> + {250, 100} + :D500_200 -> + {500, 200} + :D1000_400 -> + {1000, 400} + _ -> + :error + end + end + + def speed(_bandwidth, _technology) do + :error + end end diff --git a/test/nbn/nbn_ethernet_test.exs b/test/nbn/nbn_ethernet_test.exs index 8f3a24a..ba5899f 100644 --- a/test/nbn/nbn_ethernet_test.exs +++ b/test/nbn/nbn_ethernet_test.exs @@ -16,6 +16,7 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do alias DiffoExample.Nbn.NniGroup alias DiffoExample.Nbn.Nni alias DiffoExample.Test.Characteristics + alias Diffo.Provider.Assignment alias Diffo.Provider.Instance.Relationship setup_all do @@ -23,10 +24,9 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do end setup do - # on_exit(fn -> - # AshNeo4j.Neo4jHelper.delete_all() - # end) - :ok + on_exit(fn -> + AshNeo4j.Neo4jHelper.delete_all() + end) end describe "build nbn_ethernet" do @@ -69,7 +69,7 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do encoding = Jason.encode!(access) |> Diffo.Util.summarise_dates() assert encoding == - ~s({"id":"#{access.id}","href":"resourceInventoryManagement/v4/resource/nbnEthernet/#{access.id}","category":"Network Resource",\"name\":\"#{access.name}","resourceSpecification":{"id":"f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","href":"resourceCatalogManagement/v4/resourceSpecification/f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","name":"nbnEthernet","version":"v1.0.0"},"resourceCharacteristic":[{"name":"pri","value":{\"technology\":\"FTTP\"}}]}) + ~s({"id":"#{access.id}","href":"resourceInventoryManagement/v4/resource/nbnEthernet/#{access.id}","category":"Network Resource",\"name\":\"#{access.name}","resourceSpecification":{"id":"f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","href":"resourceCatalogManagement/v4/resourceSpecification/f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","name":"nbnEthernet","version":"v1.0.0"},"resourceCharacteristic":[{"name":"pri","value":{}}]}) end test "define nbn_ethernet access" do @@ -82,17 +82,40 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do {:ok, access} = Nbn.define_nbn_ethernet(access, %{characteristic_value_updates: updates}) Characteristics.check_values( - [pri: [avcid: "AVC000910202941", uniid: "UNI000302814545", speed: 1000, technology: :FTTP]], + [ + pri: [ + avcid: "AVC000910202941", + uniid: "UNI000302814545", + speed: 1000, + technology: :FTTP + ] + ], access ) end + @tag debug: true test "relate nbn_ethernet" do {:ok, access} = Nbn.build_nbn_ethernet(%{}) + {:ok, nni_group} = Nbn.build_nni_group(%{}) + {:ok, cvc} = Nbn.build_cvc(%{}) + {:ok, _nni_group} = Nbn.assign_svlan(nni_group, %{assignment: %Assignment{assignee_id: cvc.id, operation: :auto_assign}}) + {:ok, cvc} = Nbn.get_cvc_by_id(cvc.id, load: [:reverse_relationships]) + {:ok, cvc} = Nbn.mine_cvc(cvc) + {:ok, avc} = Nbn.build_avc(%{}) + {:ok, avc} = Nbn.define_avc(avc, %{characteristic_value_updates: [avc: [bandwidth_profile: :home_fast]]}) + {:ok, _cvc} = Nbn.assign_cvlan(cvc, %{assignment: %Assignment{assignee_id: avc.id, operation: :auto_assign}}) + {:ok, avc} = Nbn.get_avc_by_id(avc.id, load: [:reverse_relationships]) + {:ok, avc} = Nbn.mine_avc(avc) + {:ok, ntd} = Nbn.build_ntd(%{}) + {:ok, ntd} = Nbn.define_ntd(ntd, %{characteristic_value_updates: [ntd: [technology: :FTTP]]}) {:ok, uni} = Nbn.build_uni(%{}) + {:ok, _ntd} = Nbn.assign_port(ntd, %{assignment: %Assignment{assignee_id: uni.id, operation: :auto_assign}}) + {:ok, uni} = Nbn.get_uni_by_id(uni.id, load: [:reverse_relationships]) + {:ok, uni} = Nbn.mine_uni(uni) relationships = [ %Relationship{id: avc.id, direction: :forward, type: :owns, alias: :avc}, @@ -106,7 +129,7 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do encoding = Jason.encode!(access) |> Diffo.Util.summarise_dates() assert encoding == - ~s({"id":"#{access.id}","href":"resourceInventoryManagement/v4/resource/nbnEthernet/#{access.id}","category":"Network Resource","name":"#{access.name}","resourceSpecification":{"id":"f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","href":"resourceCatalogManagement/v4/resourceSpecification/f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","name":"nbnEthernet","version":"v1.0.0"},"resourceRelationship":[{"alias":"avc","type":"owns","resource":{"id":"#{avc.id}","href":"resourceInventoryManagement/v4/resource/avc/#{avc.id}"}},{"alias":"uni","type":"owns","resource":{"id\":"#{uni.id}","href":"resourceInventoryManagement/v4/resource/uni/#{uni.id}"}}],"supportingResource":[{"id":"avc","href":"resourceInventoryManagement/v4/resource/avc/#{avc.id}"},{"id\":"uni","href":"resourceInventoryManagement/v4/resource/uni/#{uni.id}"}],"resourceCharacteristic":[{"name":"pri","value":{"avcid":"#{avc.name}","uniid":"#{uni.name}","technology":"FTTP"}}]}) + ~s({"id":"#{access.id}","href":"resourceInventoryManagement/v4/resource/nbnEthernet/#{access.id}","category":"Network Resource","name":"#{access.name}","resourceSpecification":{"id":"f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","href":"resourceCatalogManagement/v4/resourceSpecification/f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","name":"nbnEthernet","version":"v1.0.0"},"resourceRelationship":[{"alias":"avc","type":"owns","resource":{"id":"#{avc.id}","href":"resourceInventoryManagement/v4/resource/avc/#{avc.id}"}},{"alias":"uni","type":"owns","resource":{"id\":"#{uni.id}","href":"resourceInventoryManagement/v4/resource/uni/#{uni.id}"}}],"supportingResource":[{"id":"avc","href":"resourceInventoryManagement/v4/resource/avc/#{avc.id}"},{"id\":"uni","href":"resourceInventoryManagement/v4/resource/uni/#{uni.id}"}],"resourceCharacteristic":[{"name":"pri","value":{"avcid":"#{avc.name}","uniid":"#{uni.name}","technology":"FTTP","bandwidth_profile":"home_fast","speeds":[500,50]}}]}) end end @@ -145,20 +168,20 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do refute is_nil(avc.specification_id) assert is_struct(avc.specification, Specification) assert is_list(avc.characteristics) - assert length(avc.characteristics) == 1 + assert length(avc.characteristics) == 2 end test "define avc" do {:ok, avc} = Nbn.build_avc(%{}) updates = [ - avc: [cir: 100, pir: 1000] + avc: [cvlan: 1, bandwidth_profile: :home_fast] ] {:ok, avc} = Nbn.define_avc(avc, %{characteristic_value_updates: updates}) Characteristics.check_values( - [avc: [cir: 100, pir: 1000]], + [avc: [cvlan: 1, bandwidth_profile: :home_fast]], avc ) end @@ -172,19 +195,50 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do refute is_nil(ntd.specification_id) end - test "define ntd" do + test "define ntd and assign ports to unis" do {:ok, ntd} = Nbn.build_ntd(%{}) updates = [ - ntd: [model: "Arris CM8200", serial_number: "SN-ABC-123456", technology: :HFC] + ntd: [model: "Sercomm CG4000A", serial_number: "SCOMA1A057A2", technology: :FTTP], + ports: [first: 1, last: 4, free: 4, type: "port"] ] {:ok, ntd} = Nbn.define_ntd(ntd, %{characteristic_value_updates: updates}) Characteristics.check_values( - [ntd: [model: "Arris CM8200", serial_number: "SN-ABC-123456", technology: :HFC]], + [ + ntd: [model: "Sercomm CG4000A", serial_number: "SCOMA1A057A2", technology: :FTTP], + ports: [first: 1, last: 4, free: 4, type: "port"] + ], + ntd + ) + + {:ok, ntd} = Nbn.assign_port(ntd, %{assignment: create_uni()}) + {:ok, ntd} = Nbn.assign_port(ntd, %{assignment: create_uni()}) + + Characteristics.check_values( + [ + ntd: [model: "Sercomm CG4000A", serial_number: "SCOMA1A057A2", technology: :FTTP], + ports: [first: 1, last: 4, free: 2, type: "port"] + ], ntd ) + + # mine and check each uni + Enum.each(ntd.forward_relationships, fn relationship -> + {:ok, uni} = + Nbn.get_uni_by_id(relationship.target_id, load: [:reverse_relationships]) + + {:ok, uni} = Nbn.mine_uni(uni) + + # uni should have an uni characteristic with the port + Characteristics.check_values( + [ + uni: [port: &Outstand.any_integer/1] + ], + uni + ) + end) end end @@ -196,19 +250,51 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do refute is_nil(cvc.specification_id) end - test "define cvc" do + test "define cvc and assign cvlans to avcs" do {:ok, cvc} = Nbn.build_cvc(%{}) updates = [ - cvc: [cvc_id: "CVC-POI-SYD-001", bandwidth: 10000] + cvc: [svlan: 1, bandwidth: 10000], + cvlans: [first: 1, last: 4000, free: 4000, type: "cvlan"] ] {:ok, cvc} = Nbn.define_cvc(cvc, %{characteristic_value_updates: updates}) Characteristics.check_values( - [cvc: [cvc_id: "CVC-POI-SYD-001", bandwidth: 10000]], + [ + cvc: [svlan: 1, bandwidth: 10000], + cvlans: [first: 1, last: 4000, free: 4000, type: "cvlan"] + ], + cvc + ) + + {:ok, cvc} = Nbn.assign_cvlan(cvc, %{assignment: create_avc()}) + {:ok, cvc} = Nbn.assign_cvlan(cvc, %{assignment: create_avc()}) + + Characteristics.check_values( + [ + cvc: [svlan: 1, bandwidth: 10000], + cvlans: [first: 1, last: 4000, free: 3998, type: "cvlan"] + ], cvc ) + + # mine and check each avc + Enum.each(cvc.forward_relationships, fn relationship -> + {:ok, avc} = + Nbn.get_avc_by_id(relationship.target_id, load: [:reverse_relationships]) + + {:ok, avc} = Nbn.mine_avc(avc) + + # avc should have an avc characteristic with the cvlan + Characteristics.check_values( + [ + avc: [cvlan: &Outstand.any_integer/1], + cvc: [svlan: :no_value] + ], + avc + ) + end) end end @@ -220,20 +306,51 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do refute is_nil(nni_group.specification_id) end - test "define nni_group" do + test "define nni_group and assign svlans to cvcs" do {:ok, nni_group} = Nbn.build_nni_group(%{}) updates = [ - nni_group: [name: "SYD-POI-01", location: "Sydney Olympic Park"] + nni_group: [name: "SYD-POI-01", location: "Sydney Olympic Park"], + svlans: [first: 1, last: 4000, free: 4000, type: "svlan"] ] {:ok, nni_group} = Nbn.define_nni_group(nni_group, %{characteristic_value_updates: updates}) Characteristics.check_values( - [nni_group: [name: "SYD-POI-01", location: "Sydney Olympic Park"]], + [ + nni_group: [name: "SYD-POI-01", location: "Sydney Olympic Park"], + svlans: [first: 1, last: 4000, free: 4000, type: "svlan"] + ], nni_group ) + + {:ok, nni_group} = Nbn.assign_svlan(nni_group, %{assignment: create_cvc()}) + {:ok, nni_group} = Nbn.assign_svlan(nni_group, %{assignment: create_cvc()}) + + Characteristics.check_values( + [ + nni_group: [name: "SYD-POI-01", location: "Sydney Olympic Park"], + svlans: [first: 1, last: 4000, free: 3998, type: "svlan"] + ], + nni_group + ) + + # mine and check each cvc + Enum.each(nni_group.forward_relationships, fn relationship -> + {:ok, cvc} = + Nbn.get_cvc_by_id(relationship.target_id, load: [:reverse_relationships]) + + {:ok, avc} = Nbn.mine_cvc(cvc) + + # cvc should have an cvc characteristic with the svlan + Characteristics.check_values( + [ + cvc: [svlan: &Outstand.any_integer/1] + ], + avc + ) + end) end end @@ -260,4 +377,19 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do ) end end + + defp create_uni() do + {:ok, uni} = Nbn.build_uni(%{}) + %Assignment{assignee_id: uni.id, operation: :auto_assign} + end + + defp create_cvc() do + {:ok, cvc} = Nbn.build_cvc(%{}) + %Assignment{assignee_id: cvc.id, operation: :auto_assign} + end + + defp create_avc() do + {:ok, avc} = Nbn.build_avc(%{}) + %Assignment{assignee_id: avc.id, operation: :auto_assign} + end end diff --git a/test/support/characteristics.ex b/test/support/characteristics.ex index 2ae4b42..5719a84 100644 --- a/test/support/characteristics.ex +++ b/test/support/characteristics.ex @@ -11,6 +11,9 @@ defmodule DiffoExample.Test.Characteristics do import Outstand import ExUnit.Assertions + @doc""" + uses Outstanding to check expected values within instance characteristics + """ def check_values(expected_values, instance) when is_list(expected_values) and is_struct(instance) do Enum.each( From 5118eb9e1dc98157691309f4bc47fd1d8e54b582 Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Fri, 17 Apr 2026 20:17:36 +0930 Subject: [PATCH 04/13] updated to refactored diffo and ash_neo4j --- .../characteristic_values/cable_value.ex | 8 ++------ .../characteristic_values/path_value.ex | 8 ++------ .../services/characteristic_values/circuit.ex | 3 +-- lib/access/util.ex | 5 +++-- mix.exs | 3 ++- mix.lock | 18 +++++++++--------- test/access/cable_test.exs | 18 +++++++++--------- test/access/card_test.exs | 18 +++++++++--------- test/access/characteristic_value_test.exs | 17 +++++++++-------- test/access/dsl_access_test.exs | 8 ++++---- test/access/path_test.exs | 6 +++--- test/access/shelf_test.exs | 15 +++++++-------- test/support/characteristics.ex | 5 +++-- 13 files changed, 63 insertions(+), 69 deletions(-) diff --git a/lib/access/resources/characteristic_values/cable_value.ex b/lib/access/resources/characteristic_values/cable_value.ex index cb8ffec..1c72707 100644 --- a/lib/access/resources/characteristic_values/cable_value.ex +++ b/lib/access/resources/characteristic_values/cable_value.ex @@ -24,13 +24,9 @@ defmodule DiffoExample.Access.CableValue do field :pairs, :integer, description: "the number of pairs in the cable" - field :length, :struct, - constraints: [instance_of: IntegerUnit], - description: "the length of the cable" + field :length, DiffoExample.Access.IntegerUnit, description: "the length of the cable" - field :loss, :struct, - constraints: [instance_of: FloatUnit], - description: "the loss of the cable at 300kHz" + field :loss, DiffoExample.Access.FloatUnit, description: "the loss of the cable at 300kHz" field :technology, :atom, description: "the cable technology" end diff --git a/lib/access/resources/characteristic_values/path_value.ex b/lib/access/resources/characteristic_values/path_value.ex index c7b5d5b..fba2f11 100644 --- a/lib/access/resources/characteristic_values/path_value.ex +++ b/lib/access/resources/characteristic_values/path_value.ex @@ -27,13 +27,9 @@ defmodule DiffoExample.Access.PathValue do constraints: [min: 0], description: "the number of sections in the path" - field :length, :struct, - constraints: [instance_of: IntegerUnit], - description: "the length of the path" + field :length, DiffoExample.Access.IntegerUnit, description: "the length of the path" - field :loss, :struct, - constraints: [instance_of: FloatUnit], - description: "the loss of the path at 300kHz" + field :loss, DiffoExample.Access.FloatUnit, description: "the loss of the path at 300kHz" field :technology, :atom, description: "the path technology" end diff --git a/lib/access/services/characteristic_values/circuit.ex b/lib/access/services/characteristic_values/circuit.ex index 7bd4b5a..c56cacb 100644 --- a/lib/access/services/characteristic_values/circuit.ex +++ b/lib/access/services/characteristic_values/circuit.ex @@ -41,8 +41,7 @@ defmodule DiffoExample.Access.Circuit do constraints: [one_of: [:PPPoA, :PPPoE, :IPoE]], description: "the circuit encapsulation" - field :bandwidth_profile, :struct, - constraints: [instance_of: BandwidthProfile], + field :bandwidth_profile, BandwidthProfile, description: "the circuit bandwidth profile" end diff --git a/lib/access/util.ex b/lib/access/util.ex index 4f21dee..0b01f0a 100644 --- a/lib/access/util.ex +++ b/lib/access/util.ex @@ -16,6 +16,7 @@ defmodule DiffoExample.Access.Util do def assignments(instance, type) do Enum.reduce(instance.reverse_relationships, [], fn reverse_relationship, acc -> IO.inspect(reverse_relationship, label: :reverse_relationship) + case reverse_relationship.type do :assignedTo -> characteristic = @@ -28,8 +29,8 @@ defmodule DiffoExample.Access.Util do _ -> [ %Assignment{ - id: characteristic.value, - type: type, + id: Diffo.Unwrap.unwrap(characteristic.value), + assignable_type: type, assignee_id: reverse_relationship.source_id } | acc diff --git a/mix.exs b/mix.exs index ce6cbe8..2230e0c 100644 --- a/mix.exs +++ b/mix.exs @@ -78,7 +78,8 @@ defmodule DiffoExample.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:diffo, diffo_version("~> 0.1.5")}, + # {:diffo, diffo_version("~> 0.1.5")}, + {:diffo, github: "diffo-dev/diffo", branch: "dev"}, {:igniter, "~> 0.6", only: [:dev, :test]}, {:ex_doc, "~> 0.37", only: [:dev, :test], runtime: false} ] diff --git a/mix.lock b/mix.lock index 34987a8..990fffb 100644 --- a/mix.lock +++ b/mix.lock @@ -1,14 +1,14 @@ %{ - "ash": {:hex, :ash, "3.19.3", "58b1bb3aea3d1d45d1c990059ffd0753409cc92fc4afe387376cb155e2a8c2a0", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.14 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "94b628319f2e144affaf1f8008277bad3340a198d48e6d2ed372990ac1643f9b"}, + "ash": {:hex, :ash, "3.24.2", "38beca133e0dcab07e3c8a7c26e573287ada26e8ba8d4c90ac692b52b34b0309", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.6.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3fd2a99504c1f58290efc3382501369ee9070098784925bdd7df9dbea8611d32"}, "ash_jason": {:hex, :ash_jason, "3.1.0", "84a88dfe5e25a20d55cf2d2664885cd086fa45871e8777aedc3ad96a282e2a6f", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:spark, ">= 2.1.21 and < 3.0.0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "71e6bbc421fb2cf7079f8804814145cca458116c839fc798f9606b806e07eb2b"}, - "ash_neo4j": {:hex, :ash_neo4j, "0.2.14", "c6bb1b895510ab423afc4225840d4c4e94716490316bf75aa8da1da464f53e0a", [:mix], [{:ash, ">= 3.19.1 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:bolty, ">= 0.0.7", [hex: :bolty, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "888bd4a9fcc3c231642dfb4cb9110d9aef8a57ff61f16f078bfd278cde17cd69"}, + "ash_neo4j": {:git, "https://github.com/diffo-dev/ash_neo4j.git", "044d9d123af30719a9f1f377e2c24b5cc8e21ea8", [branch: "dev"]}, "ash_outstanding": {:hex, :ash_outstanding, "0.2.4", "c72b91f1b8e4859fb033eddf66d0ba36cfd8af0c2a9748c7ef9e6ccfdb5d093d", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:outstanding, "~> 0.2.4", [hex: :outstanding, repo: "hexpm", optional: false]}], "hexpm", "64ba8f582ce69c9050352c75f0895db186c7a56f35039dab34c8e1ab7516f9ce"}, - "ash_state_machine": {:hex, :ash_state_machine, "0.2.12", "c0f7ebb8a176584f70c6ed196b7d0118c930d73e0590ade705d2dddc48aa7311", [:mix], [{:ash, ">= 3.4.66 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}], "hexpm", "394ce761ce82358e3c715e1cae6c5cf1390be27c03a8b661f2e5a2fda849873d"}, - "bolty": {:hex, :bolty, "0.0.7", "257889f71bd16a9291bda8290c1018123c4084c8761f905b97d7176e65d8b111", [:mix], [{:db_connection, "~> 2.7.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "fc8f88b86d292ad1336f9e246a6c0452e64774b38fac0351c141549420946e5e"}, + "ash_state_machine": {:hex, :ash_state_machine, "0.2.13", "e1c368ebf01ef73477739ee76d53e513d073b141ec11e7bf7f91d8f2d8fc9569", [:mix], [{:ash, ">= 3.4.66 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}], "hexpm", "aa21c92a8950850df69b5205bf41efc1e502f5ab839425ba08561f0421c9f226"}, + "bolty": {:hex, :bolty, "0.0.9", "c8026ce9804347f71e23b3a0cbc01b918ef94b61e159b5ba7fb48527878033ad", [:mix], [{:db_connection, "~> 2.7.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "fc20c42550c0fce370276b4ef119e92792761b2fea1aef9cccf8de946bc39d35"}, "crux": {:hex, :crux, "0.1.2", "4441c9e3a34f1e340954ce96b9ad5a2de13ceb4f97b3f910211227bb92e2ca90", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "563ea3748ebfba9cc078e6d198a1d6a06015a8fae503f0b721363139f0ddb350"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, - "diffo": {:hex, :diffo, "0.1.5", "e37d32763bd3a9b6a984523fb4f8ef9310266349576523c1d3085d7a1b8234c7", [:mix], [{:ash, ">= 3.19.1 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_jason, "~> 3.0", [hex: :ash_jason, repo: "hexpm", optional: false]}, {:ash_neo4j, "~> 0.2.14", [hex: :ash_neo4j, repo: "hexpm", optional: false]}, {:ash_outstanding, "~> 0.2.3", [hex: :ash_outstanding, repo: "hexpm", optional: false]}, {:ash_state_machine, "~> 0.2.12", [hex: :ash_state_machine, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm", "761538f03f2f79c3170413925cf8b42545063303e5c7f4a0d1923bb964528161"}, + "diffo": {:git, "https://github.com/diffo-dev/diffo.git", "9994aabd72cea1833d628195b9ef0fd097c4c885", [branch: "dev"]}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, @@ -16,7 +16,7 @@ "finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.7.6", "687d622c735e020f13cf480c83d0fce1cc899f4fbed547f5254b960ea82d3525", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "424f41a41273fce0f7424008405ee073b5bd06359ca9396e841f83a669c01619"}, + "igniter": {:hex, :igniter, "0.7.9", "8c573440b8127fd80be8220fb197e7422317a81072054fcc0b336029f035a416", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "123513d09f3af149db851aad8492b5b49f861d2c466a72031b2a0cbd9f45526f"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -30,13 +30,13 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "outstanding": {:hex, :outstanding, "0.2.5", "2f40416eb9617748cb1f8ae4c8ed94515d731f9c4fcee4f902355d30bc0792cc", [:mix], [], "hexpm", "bb47a210f0d2804ea6b8477fa6f4d15e8c58c18acee79d8e06c9296e6dd004cd"}, "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, - "reactor": {:hex, :reactor, "1.0.0", "024bd13df910bcb8c01cebed4f10bd778269a141a1c8a234e4f67796ac4883cf", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "ae8eb507fffc517f5aa5947db9d2ede2db8bae63b66c94ccb5a2027d30f830a0"}, + "reactor": {:hex, :reactor, "1.0.1", "ca3b5cf3c04ec8441e67ea2625d0294939822060b1bfd00ffdaaf75b7682d991", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "3497db2b204c9a3cabdaf1b26d2405df1dfbb138ce0ce50e616e9db19fec0043"}, "req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"}, "rewrite": {:hex, :rewrite, "1.3.0", "67448ba7975690b35ba7e7f35717efcce317dbd5963cb0577aa7325c1923121a", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "d111ac7ff3a58a802ef4f193bbd1831e00a9c57b33276e5068e8390a212714a5"}, "sourceror": {:hex, :sourceror, "1.12.0", "da354c5f35aad3cc1132f5d5b0d8437d865e2661c263260480bab51b5eedb437", [:mix], [], "hexpm", "755703683bd014ebcd5de9acc24b68fb874a660a568d1d63f8f98cd8a6ef9cd0"}, - "spark": {:hex, :spark, "2.4.1", "d6807291e74b51f6efb6dd4e0d58216ae3729d45c35c456e049556e7e946e364", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "8b065733de9840cac584515f82182ac5ba66a973a47bc5036348dc740662b46b"}, + "spark": {:hex, :spark, "2.6.1", "b0100216d3883c6a281cb2434af45afbd808695aadb034923cbaf7d8a2ba46ab", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "77bbefa5263bb6b70e1195bc0fc662ddb8ef5937a356a77ae072e56983ad13f0"}, "spitfire": {:hex, :spitfire, "0.3.10", "19aea9914132456515e8f7d592f63ab9f3130876b0252e834d2390bdd8becb24", [:mix], [], "hexpm", "6a6a5f77eb4165249c76199cd2d01fb595bac9207aed3de551918ac1c2bc9267"}, - "splode": {:hex, :splode, "0.3.0", "ff8effecc509a51245df2f864ec78d849248647c37a75886033e3b1a53ca9470", [:mix], [], "hexpm", "73cfd0892d7316d6f2c93e6e8784bd6e137b2aa38443de52fd0a25171d106d81"}, + "splode": {:hex, :splode, "0.3.1", "9843c54f84f71b7833fec3f0be06c3cfb5be6b35960ee195ea4fad84b1c25030", [:mix], [], "hexpm", "8f2309b6ec2ecbb01435656429ed1d9ed04ba28797a3280c3b0d1217018ecfbd"}, "stream_data": {:hex, :stream_data, "1.3.0", "bde37905530aff386dea1ddd86ecbf00e6642dc074ceffc10b7d4e41dfd6aac9", [:mix], [], "hexpm", "3cc552e286e817dca43c98044c706eec9318083a1480c52ae2688b08e2936e3c"}, "telemetry": {:hex, :telemetry, "1.4.1", "ab6de178e2b29b58e8256b92b382ea3f590a47152ca3651ea857a6cae05ac423", [:rebar3], [], "hexpm", "2172e05a27531d3d31dd9782841065c50dd5c3c7699d95266b2edd54c2dafa1c"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, diff --git a/test/access/cable_test.exs b/test/access/cable_test.exs index e4200a3..7de3e57 100644 --- a/test/access/cable_test.exs +++ b/test/access/cable_test.exs @@ -63,7 +63,7 @@ defmodule DiffoExample.Access.CableTest do encoding = Jason.encode!(cable) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{cable.id}",\"href\":\"resourceInventoryManagement/v4/resource/cable/#{cable.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"ce0a567a-6abb-4862-9e33-851fd79fa595\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ce0a567a-6abb-4862-9e33-851fd79fa595\",\"name\":\"cable\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"cable\",\"value\":{}},{\"name\":\"pairs\",\"value\":{\"first\":1,\"last\":1,\"free\":1,\"algorithm\":\"lowest\"}}]}) + ~s({\"id\":\"#{cable.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{cable.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"ce0a567a-6abb-4862-9e33-851fd79fa595\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ce0a567a-6abb-4862-9e33-851fd79fa595\",\"name\":\"cable\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"cable\",\"value\":{}},{\"name\":\"pairs\",\"value\":{\"first\":1,\"last\":1,\"free\":1,\"algorithm\":\"lowest\"}}]}) end test "define cable" do @@ -71,7 +71,7 @@ defmodule DiffoExample.Access.CableTest do updates = [ cable: [pairs: 60, length: %IntegerUnit{amount: 600, unit: :m}, technology: :PIUT], - pairs: [first: 1, last: 60, free: 60, type: "copper"] + pairs: [first: 1, last: 60, free: 60, assignable_type: "copper"] ] {:ok, cable} = Access.define_cable(cable, %{characteristic_value_updates: updates}) @@ -79,7 +79,7 @@ defmodule DiffoExample.Access.CableTest do encoding = Jason.encode!(cable) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{cable.id}",\"href\":\"resourceInventoryManagement/v4/resource/cable/#{cable.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"ce0a567a-6abb-4862-9e33-851fd79fa595\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ce0a567a-6abb-4862-9e33-851fd79fa595\",\"name\":\"cable\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"cable\",\"value\":{\"pairs\":60,\"length\":{\"amount\":600,\"unit\":\"m\"},\"technology\":\"PIUT\"}},{\"name\":\"pairs\",\"value\":{\"first\":1,\"last\":60,\"free\":60,\"type\":\"copper\",\"algorithm\":\"lowest\"}}]}) + ~s({\"id\":\"#{cable.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{cable.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"ce0a567a-6abb-4862-9e33-851fd79fa595\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ce0a567a-6abb-4862-9e33-851fd79fa595\",\"name\":\"cable\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"cable\",\"value\":{\"pairs\":60,\"length\":{\"amount\":600,\"unit\":\"m\"},\"technology\":\"PIUT\"}},{\"name\":\"pairs\",\"value\":{\"first\":1,\"last\":60,\"free\":60,\"type\":\"copper\",\"algorithm\":\"lowest\"}}]}) end test "auto assign pair to service" do @@ -89,7 +89,7 @@ defmodule DiffoExample.Access.CableTest do updates = [ cable: [pairs: 60, length: %IntegerUnit{amount: 600, unit: :m}, technology: :PIUT], - pairs: [first: 1, last: 60, free: 60, type: "copper"] + pairs: [first: 1, last: 60, free: 60, assignable_type: "copper"] ] {:ok, cable} = Access.define_cable(cable, %{characteristic_value_updates: updates}) @@ -104,7 +104,7 @@ defmodule DiffoExample.Access.CableTest do encoding = Jason.encode!(cable) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{cable.id}",\"href\":\"resourceInventoryManagement/v4/resource/cable/#{cable.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"ce0a567a-6abb-4862-9e33-851fd79fa595\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ce0a567a-6abb-4862-9e33-851fd79fa595\",\"name\":\"cable\",\"version\":\"v1.0.0\"},\"serviceRelationship\":[{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/dslAccess/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"pair\",\"value\":1}]}],\"resourceCharacteristic\":[{\"name\":\"cable\",\"value\":{\"pairs\":60,\"length\":{\"amount\":600,\"unit\":\"m\"},\"technology\":\"PIUT\"}},{\"name\":\"pairs\",\"value\":{\"first\":1,\"last\":60,\"free\":59,\"type\":\"copper\",\"algorithm\":\"lowest\"}}]}) + ~s({\"id\":\"#{cable.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{cable.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"ce0a567a-6abb-4862-9e33-851fd79fa595\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ce0a567a-6abb-4862-9e33-851fd79fa595\",\"name\":\"cable\",\"version\":\"v1.0.0\"},\"serviceRelationship\":[{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"pair\",\"value\":1}]}],\"resourceCharacteristic\":[{\"name\":\"cable\",\"value\":{\"pairs\":60,\"length\":{\"amount\":600,\"unit\":\"m\"},\"technology\":\"PIUT\"}},{\"name\":\"pairs\",\"value\":{\"first\":1,\"last\":60,\"free\":59,\"type\":\"copper\",\"algorithm\":\"lowest\"}}]}) end test "auto assign two pairs to same service" do @@ -114,7 +114,7 @@ defmodule DiffoExample.Access.CableTest do updates = [ cable: [pairs: 60, length: %IntegerUnit{amount: 600, unit: :m}, technology: :PIUT], - pairs: [first: 1, last: 60, free: 60, type: "copper"] + pairs: [first: 1, last: 60, free: 60, assignable_type: "copper"] ] {:ok, cable} = Access.define_cable(cable, %{characteristic_value_updates: updates}) @@ -134,7 +134,7 @@ defmodule DiffoExample.Access.CableTest do encoding = Jason.encode!(cable) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{cable.id}",\"href\":\"resourceInventoryManagement/v4/resource/cable/#{cable.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"ce0a567a-6abb-4862-9e33-851fd79fa595\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ce0a567a-6abb-4862-9e33-851fd79fa595\",\"name\":\"cable\",\"version\":\"v1.0.0\"},\"serviceRelationship\":[{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/dslAccess/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"pair\",\"value\":1}]},{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/dslAccess/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"pair\",\"value\":2}]}],\"resourceCharacteristic\":[{\"name\":\"cable\",\"value\":{\"pairs\":60,\"length\":{\"amount\":600,\"unit\":\"m\"},\"technology\":\"PIUT\"}},{\"name\":\"pairs\",\"value\":{\"first\":1,\"last\":60,\"free\":58,\"type\":\"copper\",\"algorithm\":\"lowest\"}}]}) + ~s({\"id\":\"#{cable.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{cable.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"ce0a567a-6abb-4862-9e33-851fd79fa595\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ce0a567a-6abb-4862-9e33-851fd79fa595\",\"name\":\"cable\",\"version\":\"v1.0.0\"},\"serviceRelationship\":[{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"pair\",\"value\":1}]},{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"pair\",\"value\":2}]}],\"resourceCharacteristic\":[{\"name\":\"cable\",\"value\":{\"pairs\":60,\"length\":{\"amount\":600,\"unit\":\"m\"},\"technology\":\"PIUT\"}},{\"name\":\"pairs\",\"value\":{\"first\":1,\"last\":60,\"free\":58,\"type\":\"copper\",\"algorithm\":\"lowest\"}}]}) end test "specific assignment rejects duplicate request" do @@ -144,7 +144,7 @@ defmodule DiffoExample.Access.CableTest do updates = [ cable: [pairs: 60, length: %IntegerUnit{amount: 600, unit: :m}, technology: :PIUT], - pairs: [first: 1, last: 60, free: 60, type: "copper"] + pairs: [first: 1, last: 60, free: 60, assignable_type: "copper"] ] {:ok, cable} = Access.define_cable(cable, %{characteristic_value_updates: updates}) @@ -164,7 +164,7 @@ defmodule DiffoExample.Access.CableTest do encoding = Jason.encode!(cable) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{cable.id}",\"href\":\"resourceInventoryManagement/v4/resource/cable/#{cable.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"ce0a567a-6abb-4862-9e33-851fd79fa595\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ce0a567a-6abb-4862-9e33-851fd79fa595\",\"name\":\"cable\",\"version\":\"v1.0.0\"},\"serviceRelationship\":[{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/dslAccess/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"pair\",\"value\":5}]}],\"resourceCharacteristic\":[{\"name\":\"cable\",\"value\":{\"pairs\":60,\"length\":{\"amount\":600,\"unit\":\"m\"},\"technology\":\"PIUT\"}},{\"name\":\"pairs\",\"value\":{\"first\":1,\"last\":60,\"free\":59,\"type\":\"copper\",\"algorithm\":\"lowest\"}}]}) + ~s({\"id\":\"#{cable.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{cable.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"ce0a567a-6abb-4862-9e33-851fd79fa595\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ce0a567a-6abb-4862-9e33-851fd79fa595\",\"name\":\"cable\",\"version\":\"v1.0.0\"},\"serviceRelationship\":[{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"pair\",\"value\":5}]}],\"resourceCharacteristic\":[{\"name\":\"cable\",\"value\":{\"pairs\":60,\"length\":{\"amount\":600,\"unit\":\"m\"},\"technology\":\"PIUT\"}},{\"name\":\"pairs\",\"value\":{\"first\":1,\"last\":60,\"free\":59,\"type\":\"copper\",\"algorithm\":\"lowest\"}}]}) end end end diff --git a/test/access/card_test.exs b/test/access/card_test.exs index 451e7cf..67d1942 100644 --- a/test/access/card_test.exs +++ b/test/access/card_test.exs @@ -62,7 +62,7 @@ defmodule DiffoExample.Access.CardTest do encoding = Jason.encode!(card) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{card.id}",\"href\":\"resourceInventoryManagement/v4/resource/card/#{card.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"name\":\"card\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"card\",\"value\":{}},{\"name\":\"ports\",\"value\":{\"first\":1,\"last\":1,\"free\":1,\"algorithm\":\"lowest\"}}]}) + ~s({\"id\":\"#{card.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{card.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"name\":\"card\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"card\",\"value\":{}},{\"name\":\"ports\",\"value\":{\"first\":1,\"last\":1,\"free\":1,\"algorithm\":\"lowest\"}}]}) end test "define card" do @@ -70,7 +70,7 @@ defmodule DiffoExample.Access.CardTest do updates = [ card: [family: :ISAM, model: "EBLT48", technology: :adsl2Plus], - ports: [first: 1, last: 48, free: 48, type: "ADSL2+"] + ports: [first: 1, last: 48, free: 48, assignable_type: "ADSL2+"] ] {:ok, card} = Access.define_card(card, %{characteristic_value_updates: updates}) @@ -78,7 +78,7 @@ defmodule DiffoExample.Access.CardTest do encoding = Jason.encode!(card) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{card.id}",\"href\":\"resourceInventoryManagement/v4/resource/card/#{card.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"name\":\"card\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"card\",\"value\":{\"family\":\"ISAM\",\"model\":\"EBLT48\",\"technology\":\"adsl2Plus\"}},{\"name\":\"ports\",\"value\":{\"first\":1,\"last\":48,\"free\":48,\"type\":\"ADSL2+\",\"algorithm\":\"lowest\"}}]}) + ~s({\"id\":\"#{card.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{card.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"name\":\"card\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"card\",\"value\":{\"family\":\"ISAM\",\"model\":\"EBLT48\",\"technology\":\"adsl2Plus\"}},{\"name\":\"ports\",\"value\":{\"first\":1,\"last\":48,\"free\":48,\"type\":\"ADSL2+\",\"algorithm\":\"lowest\"}}]}) end test "auto assign port to service" do @@ -88,7 +88,7 @@ defmodule DiffoExample.Access.CardTest do updates = [ card: [family: :ISAM, model: "EBLT48", technology: :adsl2Plus], - ports: [first: 1, last: 48, free: 48, type: "ADSL2+"] + ports: [first: 1, last: 48, free: 48, assignable_type: "ADSL2+"] ] {:ok, card} = Access.define_card(card, %{characteristic_value_updates: updates}) @@ -103,7 +103,7 @@ defmodule DiffoExample.Access.CardTest do encoding = Jason.encode!(card) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{card.id}",\"href\":\"resourceInventoryManagement/v4/resource/card/#{card.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"name\":\"card\",\"version\":\"v1.0.0\"},\"serviceRelationship\":[{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/dslAccess/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"port\",\"value\":1}]}],\"resourceCharacteristic\":[{\"name\":\"card\",\"value\":{\"family\":\"ISAM\",\"model\":\"EBLT48\",\"technology\":\"adsl2Plus\"}},{\"name\":\"ports\",\"value\":{\"first\":1,\"last\":48,\"free\":47,\"type\":\"ADSL2+\",\"algorithm\":\"lowest\"}}]}) + ~s({\"id\":\"#{card.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{card.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"name\":\"card\",\"version\":\"v1.0.0\"},\"serviceRelationship\":[{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"port\",\"value\":1}]}],\"resourceCharacteristic\":[{\"name\":\"card\",\"value\":{\"family\":\"ISAM\",\"model\":\"EBLT48\",\"technology\":\"adsl2Plus\"}},{\"name\":\"ports\",\"value\":{\"first\":1,\"last\":48,\"free\":47,\"type\":\"ADSL2+\",\"algorithm\":\"lowest\"}}]}) end test "auto assign two ports to same service" do @@ -113,7 +113,7 @@ defmodule DiffoExample.Access.CardTest do updates = [ card: [family: :ISAM, model: "EBLT48", technology: :adsl2Plus], - ports: [first: 1, last: 48, free: 48, type: "ADSL2+"] + ports: [first: 1, last: 48, free: 48, assignable_type: "ADSL2+"] ] {:ok, card} = Access.define_card(card, %{characteristic_value_updates: updates}) @@ -133,7 +133,7 @@ defmodule DiffoExample.Access.CardTest do encoding = Jason.encode!(card) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{card.id}",\"href\":\"resourceInventoryManagement/v4/resource/card/#{card.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"name\":\"card\",\"version\":\"v1.0.0\"},\"serviceRelationship\":[{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/dslAccess/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"port\",\"value\":1}]},{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/dslAccess/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"port\",\"value\":2}]}],\"resourceCharacteristic\":[{\"name\":\"card\",\"value\":{\"family\":\"ISAM\",\"model\":\"EBLT48\",\"technology\":\"adsl2Plus\"}},{\"name\":\"ports\",\"value\":{\"first\":1,\"last\":48,\"free\":46,\"type\":\"ADSL2+\",\"algorithm\":\"lowest\"}}]}) + ~s({\"id\":\"#{card.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{card.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"name\":\"card\",\"version\":\"v1.0.0\"},\"serviceRelationship\":[{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"port\",\"value\":1}]},{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"port\",\"value\":2}]}],\"resourceCharacteristic\":[{\"name\":\"card\",\"value\":{\"family\":\"ISAM\",\"model\":\"EBLT48\",\"technology\":\"adsl2Plus\"}},{\"name\":\"ports\",\"value\":{\"first\":1,\"last\":48,\"free\":46,\"type\":\"ADSL2+\",\"algorithm\":\"lowest\"}}]}) end test "specific assignment rejects duplicate request" do @@ -143,7 +143,7 @@ defmodule DiffoExample.Access.CardTest do updates = [ card: [family: :ISAM, model: "EBLT48", technology: :adsl2Plus], - ports: [first: 1, last: 48, free: 48, type: "ADSL2+"] + ports: [first: 1, last: 48, free: 48, assignable_type: "ADSL2+"] ] {:ok, card} = Access.define_card(card, %{characteristic_value_updates: updates}) @@ -163,7 +163,7 @@ defmodule DiffoExample.Access.CardTest do encoding = Jason.encode!(card) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{card.id}",\"href\":\"resourceInventoryManagement/v4/resource/card/#{card.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"name\":\"card\",\"version\":\"v1.0.0\"},\"serviceRelationship\":[{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/dslAccess/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"port\",\"value\":5}]}],\"resourceCharacteristic\":[{\"name\":\"card\",\"value\":{\"family\":\"ISAM\",\"model\":\"EBLT48\",\"technology\":\"adsl2Plus\"}},{\"name\":\"ports\",\"value\":{\"first\":1,\"last\":48,\"free\":47,\"type\":\"ADSL2+\",\"algorithm\":\"lowest\"}}]}) + ~s({\"id\":\"#{card.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{card.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"name\":\"card\",\"version\":\"v1.0.0\"},\"serviceRelationship\":[{\"type\":\"assignedTo\",\"service\":{\"id\":\"#{assignee.id}\",\"href\":\"serviceInventoryManagement/v4/service/#{assignee.id}\"},\"serviceRelationshipCharacteristic\":[{\"name\":\"port\",\"value\":5}]}],\"resourceCharacteristic\":[{\"name\":\"card\",\"value\":{\"family\":\"ISAM\",\"model\":\"EBLT48\",\"technology\":\"adsl2Plus\"}},{\"name\":\"ports\",\"value\":{\"first\":1,\"last\":48,\"free\":47,\"type\":\"ADSL2+\",\"algorithm\":\"lowest\"}}]}) end end end diff --git a/test/access/characteristic_value_test.exs b/test/access/characteristic_value_test.exs index 63c4fbd..107ce72 100644 --- a/test/access/characteristic_value_test.exs +++ b/test/access/characteristic_value_test.exs @@ -10,6 +10,7 @@ defmodule DiffoExample.Access.CharacteristicValueTest do alias DiffoExample.Access.Dslam alias DiffoExample.Access.Line alias DiffoExample.Access.BandwidthProfile + alias Diffo.Type.Value setup_all do AshNeo4j.BoltyHelper.start() @@ -32,8 +33,7 @@ defmodule DiffoExample.Access.CharacteristicValueTest do describe "DiffoExample.Access create Characteristics" do test "create characteristics" do - dslam_value = - Dslam.new!(%{name: @dslam, model: @model}) + dslam_value = Value.dynamic(Dslam, Dslam.new!(%{name: @dslam, model: @model})) dslam = Diffo.Provider.create_characteristic!(%{ @@ -47,12 +47,12 @@ defmodule DiffoExample.Access.CharacteristicValueTest do assert encoding == ~s({\"name\":\"dslam\",\"value\":{\"name\":\"#{@dslam}\",\"family\":\"ISAM",\"model\":\"#{@model}\",\"technology\":\"eth\"}}) - aggregate_interface_value = + aggregate_interface_value = Value.dynamic(AggregateInterface, AggregateInterface.new!(%{ name: "F DONC BOXH 010J", physical_interface: "1000BASE-LX", svlan_id: @svlan_id - }) + })) aggregate_interface = Diffo.Provider.create_characteristic!(%{ @@ -69,8 +69,8 @@ defmodule DiffoExample.Access.CharacteristicValueTest do assert Jason.encode!(bandwidth_profile) == ~s({\"downstream\":24,\"upstream\":1,\"units\":\"Mbps\"}) - {:ok, circuit_value} = - Circuit.new(%{ + circuit_value = Value.dynamic(Circuit, + Circuit.new!%{ circuit_id: @circuit_id, cvlan_id: @cvlan_id, bandwidth_profile: bandwidth_profile @@ -86,8 +86,9 @@ defmodule DiffoExample.Access.CharacteristicValueTest do assert Jason.encode!(circuit) == ~s({\"name\":\"circuit\",\"value\":{\"circuit_id\":\"#{@circuit_id}\",\"cvlan_id\":82,\"vci\":0,\"encapsulation\":\"IPoE\",\"bandwidth_profile\":{\"downstream\":24,\"upstream\":1,\"units\":\"Mbps\"}}}) - {:ok, line_value} = - Line.new(%{port: @port, slot: @slot, standard: :ADSL2plus, profile: @profile}) + line_value = Value.dynamic(Line, + Line.new!(%{port: @port, slot: @slot, standard: :ADSL2plus, profile: @profile}) + ) line = Diffo.Provider.create_characteristic!(%{ diff --git a/test/access/dsl_access_test.exs b/test/access/dsl_access_test.exs index 2f816ce..41a7e41 100644 --- a/test/access/dsl_access_test.exs +++ b/test/access/dsl_access_test.exs @@ -106,7 +106,7 @@ defmodule DiffoExample.Access.DslAccessTest do encoding = Jason.encode!(dsl_access) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{dsl_access.id}",\"href\":\"serviceInventoryManagement/v4/service/dslAccess/#{dsl_access.id}\",\"category\":\"Network Service\",\"serviceSpecification\":{\"id\":\"da9b207a-26c3-451d-8abd-0640c6349979\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/da9b207a-26c3-451d-8abd-0640c6349979\",\"name\":\"dslAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"initial\",\"feature\":[{\"name\":\"dynamic_line_management\",\"isEnabled\":true,\"featureCharacteristic\":[{\"name\":\"constraints\",\"value\":{}}]}],\"serviceCharacteristic\":[{\"name\":\"aggregate_interface\",\"value\":{\"physical_layer\":\"GbE\",\"link_layer\":\"QinQ\",\"svlan_id\":0,\"vpi\":0}},{\"name\":\"circuit\",\"value\":{\"cvlan_id\":0,\"vci\":0,\"encapsulation\":\"IPoE\"}},{\"name\":\"dslam\",\"value\":{\"family\":\"ISAM\",\"technology\":\"eth\"}},{\"name\":\"line\",\"value\":{\"standard\":\"ADSL2plus\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"IND000000897354\",\"name\":\"individualId\",\"role\":\"Customer\",\"@referredType\":\"Individual\",\"@type\":\"PartyRef\"},{\"id\":\"ORG000000123456\",\"name\":\"organizationId\",\"role\":\"Reseller\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) + ~s({\"id\":\"#{dsl_access.id}",\"href\":\"serviceInventoryManagement/v4/service/#{dsl_access.id}\",\"category\":\"Network Service\",\"serviceSpecification\":{\"id\":\"da9b207a-26c3-451d-8abd-0640c6349979\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/da9b207a-26c3-451d-8abd-0640c6349979\",\"name\":\"dslAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"initial\",\"feature\":[{\"name\":\"dynamic_line_management\",\"isEnabled\":true,\"featureCharacteristic\":[{\"name\":\"constraints\",\"value\":{}}]}],\"serviceCharacteristic\":[{\"name\":\"aggregate_interface\",\"value\":{\"physical_layer\":\"GbE\",\"link_layer\":\"QinQ\",\"svlan_id\":0,\"vpi\":0}},{\"name\":\"circuit\",\"value\":{\"cvlan_id\":0,\"vci\":0,\"encapsulation\":\"IPoE\"}},{\"name\":\"dslam\",\"value\":{\"family\":\"ISAM\",\"technology\":\"eth\"}},{\"name\":\"line\",\"value\":{\"standard\":\"ADSL2plus\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"IND000000897354\",\"name\":\"individualId\",\"role\":\"Customer\",\"@referredType\":\"Individual\",\"@type\":\"PartyRef\"},{\"id\":\"ORG000000123456\",\"name\":\"organizationId\",\"role\":\"Reseller\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) end test "advance service to feasibilityChecked" do @@ -134,7 +134,7 @@ defmodule DiffoExample.Access.DslAccessTest do encoding = Jason.encode!(dsl_access) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{dsl_access.id}",\"href\":\"serviceInventoryManagement/v4/service/dslAccess/#{dsl_access.id}\",\"category\":\"Network Service\",\"serviceSpecification\":{\"id\":\"da9b207a-26c3-451d-8abd-0640c6349979\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/da9b207a-26c3-451d-8abd-0640c6349979\",\"name\":\"dslAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"feasibilityChecked\",\"operatingStatus\":\"feasible\",\"feature\":[{\"name\":\"dynamic_line_management\",\"isEnabled\":true,\"featureCharacteristic\":[{\"name\":\"constraints\",\"value\":{}}]}],\"serviceCharacteristic\":[{\"name\":\"aggregate_interface\",\"value\":{\"physical_layer\":\"GbE\",\"link_layer\":\"QinQ\",\"svlan_id\":0,\"vpi\":0}},{\"name\":\"circuit\",\"value\":{\"cvlan_id\":0,\"vci\":0,\"encapsulation\":\"IPoE\"}},{\"name\":\"dslam\",\"value\":{\"family\":\"ISAM\",\"technology\":\"eth\"}},{\"name\":\"line\",\"value\":{\"standard\":\"ADSL2plus\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"IND000000897354\",\"name\":\"individualId\",\"role\":\"Customer\",\"@referredType\":\"Individual\",\"@type\":\"PartyRef\"},{\"id\":\"ORG000000123456\",\"name\":\"organizationId\",\"role\":\"Reseller\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) + ~s({\"id\":\"#{dsl_access.id}",\"href\":\"serviceInventoryManagement/v4/service/#{dsl_access.id}\",\"category\":\"Network Service\",\"serviceSpecification\":{\"id\":\"da9b207a-26c3-451d-8abd-0640c6349979\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/da9b207a-26c3-451d-8abd-0640c6349979\",\"name\":\"dslAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"feasibilityChecked\",\"operatingStatus\":\"feasible\",\"feature\":[{\"name\":\"dynamic_line_management\",\"isEnabled\":true,\"featureCharacteristic\":[{\"name\":\"constraints\",\"value\":{}}]}],\"serviceCharacteristic\":[{\"name\":\"aggregate_interface\",\"value\":{\"physical_layer\":\"GbE\",\"link_layer\":\"QinQ\",\"svlan_id\":0,\"vpi\":0}},{\"name\":\"circuit\",\"value\":{\"cvlan_id\":0,\"vci\":0,\"encapsulation\":\"IPoE\"}},{\"name\":\"dslam\",\"value\":{\"family\":\"ISAM\",\"technology\":\"eth\"}},{\"name\":\"line\",\"value\":{\"standard\":\"ADSL2plus\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"IND000000897354\",\"name\":\"individualId\",\"role\":\"Customer\",\"@referredType\":\"Individual\",\"@type\":\"PartyRef\"},{\"id\":\"ORG000000123456\",\"name\":\"organizationId\",\"role\":\"Reseller\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) end end @@ -155,7 +155,7 @@ defmodule DiffoExample.Access.DslAccessTest do # and we allocate the backhaul interface, svlan and cvlan, so can derive the cicuit id updates = [ - dslam: [name: QDONC0001, model: ISAM7330], + dslam: [name: :QDONC0001, model: :ISAM7330], aggregate_interface: [name: "eth0", svlan_id: 3108], circuit: [cvlan_id: 82], line: [slot: 10, port: 5] @@ -175,7 +175,7 @@ defmodule DiffoExample.Access.DslAccessTest do encoding = Jason.encode!(dsl_access) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{dsl_access.id}",\"href\":\"serviceInventoryManagement/v4/service/dslAccess/#{dsl_access.id}\",\"category\":\"Network Service\",\"serviceSpecification\":{\"id\":\"da9b207a-26c3-451d-8abd-0640c6349979\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/da9b207a-26c3-451d-8abd-0640c6349979\",\"name\":\"dslAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"reserved\",\"operatingStatus\":\"feasible\",\"feature\":[{\"name\":\"dynamic_line_management\",\"isEnabled\":true,\"featureCharacteristic\":[{\"name\":\"constraints\",\"value\":{}}]}],\"serviceCharacteristic\":[{\"name\":\"aggregate_interface\",\"value\":{\"name\":\"eth0\",\"physical_layer\":\"GbE\",\"link_layer\":\"QinQ\",\"svlan_id\":3108,\"vpi\":0}},{\"name\":\"circuit\",\"value\":{\"cvlan_id\":82,\"vci\":0,\"encapsulation\":\"IPoE\"}},{\"name\":\"dslam\",\"value\":{\"name\":\"QDONC0001\",\"family\":\"ISAM\",\"model\":\"ISAM7330\",\"technology\":\"eth\"}},{\"name\":\"line\",\"value\":{\"port\":5,\"slot\":10,\"standard\":\"ADSL2plus\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"IND000000897354\",\"name\":\"individualId\",\"role\":\"Customer\",\"@referredType\":\"Individual\",\"@type\":\"PartyRef\"},{\"id\":\"ORG000000123456\",\"name\":\"organizationId\",\"role\":\"Reseller\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) + ~s({\"id\":\"#{dsl_access.id}",\"href\":\"serviceInventoryManagement/v4/service/#{dsl_access.id}\",\"category\":\"Network Service\",\"serviceSpecification\":{\"id\":\"da9b207a-26c3-451d-8abd-0640c6349979\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/da9b207a-26c3-451d-8abd-0640c6349979\",\"name\":\"dslAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"reserved\",\"operatingStatus\":\"feasible\",\"feature\":[{\"name\":\"dynamic_line_management\",\"isEnabled\":true,\"featureCharacteristic\":[{\"name\":\"constraints\",\"value\":{}}]}],\"serviceCharacteristic\":[{\"name\":\"aggregate_interface\",\"value\":{\"name\":\"eth0\",\"physical_layer\":\"GbE\",\"link_layer\":\"QinQ\",\"svlan_id\":3108,\"vpi\":0}},{\"name\":\"circuit\",\"value\":{\"cvlan_id\":82,\"vci\":0,\"encapsulation\":\"IPoE\"}},{\"name\":\"dslam\",\"value\":{\"name\":\"QDONC0001\",\"family\":\"ISAM\",\"model\":\"ISAM7330\",\"technology\":\"eth\"}},{\"name\":\"line\",\"value\":{\"port\":5,\"slot\":10,\"standard\":\"ADSL2plus\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"IND000000897354\",\"name\":\"individualId\",\"role\":\"Customer\",\"@referredType\":\"Individual\",\"@type\":\"PartyRef\"},{\"id\":\"ORG000000123456\",\"name\":\"organizationId\",\"role\":\"Reseller\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) end end diff --git a/test/access/path_test.exs b/test/access/path_test.exs index ea5e9b8..9ddde4e 100644 --- a/test/access/path_test.exs +++ b/test/access/path_test.exs @@ -74,7 +74,7 @@ defmodule DiffoExample.Access.PathTest do encoding = Jason.encode!(path) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{path.id}",\"href\":\"resourceInventoryManagement/v4/resource/path/#{path.id}",\"category\":\"Network Resource\",\"name\":\"82 Rathmullen - DONC\",\"resourceSpecification\":{\"id\":\"1d507914-8f76-48cb-aa0e-3a8f92951ab0\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/1d507914-8f76-48cb-aa0e-3a8f92951ab0\",\"name\":\"path\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"path\",\"value\":{\"sections\":0}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC\",\"href\":\"place/telco/DONC\",\"name\":\"exchangeId\",\"role\":\"NetworkSite\",\"@referredType\":\"GeographicSite\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) + ~s({\"id\":\"#{path.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{path.id}",\"category\":\"Network Resource\",\"name\":\"82 Rathmullen - DONC\",\"resourceSpecification\":{\"id\":\"1d507914-8f76-48cb-aa0e-3a8f92951ab0\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/1d507914-8f76-48cb-aa0e-3a8f92951ab0\",\"name\":\"path\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"path\",\"value\":{\"sections\":0}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC\",\"href\":\"place/telco/DONC\",\"name\":\"exchangeId\",\"role\":\"NetworkSite\",\"@referredType\":\"GeographicSite\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) end end @@ -94,7 +94,7 @@ defmodule DiffoExample.Access.PathTest do encoding = Jason.encode!(path) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{path.id}",\"href\":\"resourceInventoryManagement/v4/resource/path/#{path.id}",\"category\":\"Network Resource\",\"name\":\"82 Rathmullen - DONC\",\"resourceSpecification\":{\"id\":\"1d507914-8f76-48cb-aa0e-3a8f92951ab0\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/1d507914-8f76-48cb-aa0e-3a8f92951ab0\",\"name\":\"path\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"path\",\"value\":{\"name\":\"82 Rathmullen - DONC\",\"sections\":0,\"technology\":\"copper\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC\",\"href\":\"place/telco/DONC\",\"name\":\"exchangeId\",\"role\":\"NetworkSite\",\"@referredType\":\"GeographicSite\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) + ~s({\"id\":\"#{path.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{path.id}",\"category\":\"Network Resource\",\"name\":\"82 Rathmullen - DONC\",\"resourceSpecification\":{\"id\":\"1d507914-8f76-48cb-aa0e-3a8f92951ab0\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/1d507914-8f76-48cb-aa0e-3a8f92951ab0\",\"name\":\"path\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"path\",\"value\":{\"name\":\"82 Rathmullen - DONC\",\"sections\":0,\"technology\":\"copper\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC\",\"href\":\"place/telco/DONC\",\"name\":\"exchangeId\",\"role\":\"NetworkSite\",\"@referredType\":\"GeographicSite\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) end test "relate cables and dslam" do @@ -138,7 +138,7 @@ defmodule DiffoExample.Access.PathTest do # the reverse relationships are not encoded to json assert encoding == - ~s({\"id\":\"#{path.id}",\"href\":\"resourceInventoryManagement/v4/resource/path/#{path.id}",\"category\":\"Network Resource\",\"name\":\"82 Rathmullen - DONC\",\"resourceSpecification\":{\"id\":\"1d507914-8f76-48cb-aa0e-3a8f92951ab0\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/1d507914-8f76-48cb-aa0e-3a8f92951ab0\",\"name\":\"path\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"path\",\"value\":{\"name\":\"82 Rathmullen - DONC\",\"sections\":0,\"technology\":\"copper\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC\",\"href\":\"place/telco/DONC\",\"name\":\"exchangeId\",\"role\":\"NetworkSite\",\"@referredType\":\"GeographicSite\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) + ~s({\"id\":\"#{path.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{path.id}",\"category\":\"Network Resource\",\"name\":\"82 Rathmullen - DONC\",\"resourceSpecification\":{\"id\":\"1d507914-8f76-48cb-aa0e-3a8f92951ab0\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/1d507914-8f76-48cb-aa0e-3a8f92951ab0\",\"name\":\"path\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"path\",\"value\":{\"name\":\"82 Rathmullen - DONC\",\"sections\":0,\"technology\":\"copper\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC\",\"href\":\"place/telco/DONC\",\"name\":\"exchangeId\",\"role\":\"NetworkSite\",\"@referredType\":\"GeographicSite\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) end defp create_customer_place do diff --git a/test/access/shelf_test.exs b/test/access/shelf_test.exs index f49f692..977d4f5 100644 --- a/test/access/shelf_test.exs +++ b/test/access/shelf_test.exs @@ -74,7 +74,7 @@ defmodule DiffoExample.Access.ShelfTest do encoding = Jason.encode!(shelf) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{shelf.id}",\"href\":\"resourceInventoryManagement/v4/resource/shelf/#{shelf.id}",\"category\":\"Network Resource\",\"name\":\"QDONC-0001\",\"resourceSpecification\":{\"id\":\"ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"name\":\"shelf\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"shelf\",\"value\":{}},{\"name\":\"slots\",\"value\":{\"first\":1,\"last\":1,\"free\":1,\"algorithm\":\"lowest\"}}],\"place\":[{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) + ~s({\"id\":\"#{shelf.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{shelf.id}",\"category\":\"Network Resource\",\"name\":\"QDONC-0001\",\"resourceSpecification\":{\"id\":\"ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"name\":\"shelf\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"shelf\",\"value\":{}},{\"name\":\"slots\",\"value\":{\"first\":1,\"last\":1,\"free\":1,\"algorithm\":\"lowest\"}}],\"place\":[{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) end end @@ -85,7 +85,7 @@ defmodule DiffoExample.Access.ShelfTest do updates = [ shelf: [name: "QDONC-1001", family: :ISAM, model: "ISAM7330", technology: :DSLAM], - slots: [first: 1, last: 10, free: 10, type: "LineCard"] + slots: [first: 1, last: 10, free: 10, assignable_type: "LineCard"] ] {:ok, shelf} = Access.define_shelf(shelf, %{characteristic_value_updates: updates}) @@ -93,7 +93,7 @@ defmodule DiffoExample.Access.ShelfTest do encoding = Jason.encode!(shelf) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{shelf.id}",\"href\":\"resourceInventoryManagement/v4/resource/shelf/#{shelf.id}",\"category\":\"Network Resource\",\"name\":\"QDONC-0001\",\"resourceSpecification\":{\"id\":\"ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"name\":\"shelf\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"shelf\",\"value\":{\"name\":\"QDONC-1001\",\"family\":\"ISAM\",\"model\":\"ISAM7330\",\"technology\":\"DSLAM\"}},{\"name\":\"slots\",\"value\":{\"first\":1,\"last\":10,\"free\":10,\"type\":\"LineCard\",\"algorithm\":\"lowest\"}}],\"place\":[{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) + ~s({\"id\":\"#{shelf.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{shelf.id}",\"category\":\"Network Resource\",\"name\":\"QDONC-0001\",\"resourceSpecification\":{\"id\":\"ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"name\":\"shelf\",\"version\":\"v1.0.0\"},\"resourceCharacteristic\":[{\"name\":\"shelf\",\"value\":{\"name\":\"QDONC-1001\",\"family\":\"ISAM\",\"model\":\"ISAM7330\",\"technology\":\"DSLAM\"}},{\"name\":\"slots\",\"value\":{\"first\":1,\"last\":10,\"free\":10,\"type\":\"LineCard\",\"algorithm\":\"lowest\"}}],\"place\":[{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) end test "relate common cards" do @@ -104,7 +104,7 @@ defmodule DiffoExample.Access.ShelfTest do updates = [ shelf: [name: "QDONC-1001", family: :ISAM, model: "ISAM7330", technology: :DSLAM], - slots: [first: 1, last: 10, free: 10, type: "LineCard"] + slots: [first: 1, last: 10, free: 10, assignable_type: "LineCard"] ] {:ok, shelf} = Access.define_shelf(shelf, %{characteristic_value_updates: updates}) @@ -119,10 +119,9 @@ defmodule DiffoExample.Access.ShelfTest do # resource relationships are sorted in the create order of the relationships assert encoding == - ~s({\"id\":\"#{shelf.id}",\"href\":\"resourceInventoryManagement/v4/resource/shelf/#{shelf.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"name\":\"shelf\",\"version\":\"v1.0.0\"},\"resourceRelationship\":[{\"type\":\"contains\",\"resource\":{\"id\":\"#{card0.id}\",\"href\":\"resourceInventoryManagement/v4/resource/card/#{card0.id}\"}},{\"type\":\"contains\",\"resource\":{\"id\":\"#{card1.id}\",\"href\":\"resourceInventoryManagement/v4/resource/card/#{card1.id}\"}},{\"type\":\"contains\",\"resource\":{\"id\":\"#{card2.id}\",\"href\":\"resourceInventoryManagement/v4/resource/card/#{card2.id}\"}},{\"type\":\"contains\",\"resource\":{\"id\":\"#{card3.id}\",\"href\":\"resourceInventoryManagement/v4/resource/card/#{card3.id}\"}}],\"resourceCharacteristic\":[{\"name\":\"shelf\",\"value\":{\"name\":\"QDONC-1001\",\"family\":\"ISAM\",\"model\":\"ISAM7330\",\"technology\":\"DSLAM\"}},{\"name\":\"slots\",\"value\":{\"first\":1,\"last\":10,\"free\":10,\"type\":\"LineCard\",\"algorithm\":\"lowest\"}}],\"place\":[{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) + ~s({\"id\":\"#{shelf.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{shelf.id}",\"category\":\"Network Resource\",\"resourceSpecification\":{\"id\":\"ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"name\":\"shelf\",\"version\":\"v1.0.0\"},\"resourceRelationship\":[{\"type\":\"contains\",\"resource\":{\"id\":\"#{card0.id}\",\"href\":\"resourceInventoryManagement/v4/resource/#{card0.id}\"}},{\"type\":\"contains\",\"resource\":{\"id\":\"#{card1.id}\",\"href\":\"resourceInventoryManagement/v4/resource/#{card1.id}\"}},{\"type\":\"contains\",\"resource\":{\"id\":\"#{card2.id}\",\"href\":\"resourceInventoryManagement/v4/resource/#{card2.id}\"}},{\"type\":\"contains\",\"resource\":{\"id\":\"#{card3.id}\",\"href\":\"resourceInventoryManagement/v4/resource/#{card3.id}\"}}],\"resourceCharacteristic\":[{\"name\":\"shelf\",\"value\":{\"name\":\"QDONC-1001\",\"family\":\"ISAM\",\"model\":\"ISAM7330\",\"technology\":\"DSLAM\"}},{\"name\":\"slots\",\"value\":{\"first\":1,\"last\":10,\"free\":10,\"type\":\"LineCard\",\"algorithm\":\"lowest\"}}],\"place\":[{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) end - @tag debug: true test "auto assign line cards" do places = [create_esa_place()] parties = [create_provider_party()] @@ -131,7 +130,7 @@ defmodule DiffoExample.Access.ShelfTest do updates = [ shelf: [name: "QDONC-1001", family: :ISAM, model: "ISAM7330", technology: :DSLAM], - slots: [first: 1, last: 10, free: 10, type: "LineCard"] + slots: [first: 1, last: 10, free: 10, assignable_type: "LineCard"] ] {:ok, shelf} = Access.define_shelf(shelf, %{characteristic_value_updates: updates}) @@ -149,7 +148,7 @@ defmodule DiffoExample.Access.ShelfTest do lc2 = line_card2.assignee_id assert encoding == - ~s({\"id\":\"#{shelf.id}",\"href\":\"resourceInventoryManagement/v4/resource/shelf/#{shelf.id}",\"category\":\"Network Resource\",\"name\":\"QDONC-0001\",\"resourceSpecification\":{\"id\":\"ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"name\":\"shelf\",\"version\":\"v1.0.0\"},\"resourceRelationship\":[{\"type\":\"assignedTo\",\"resource\":{\"id\":\"#{lc1}\",\"href\":\"resourceInventoryManagement/v4/resource/card/#{lc1}\"},\"resourceRelationshipCharacteristic\":[{\"name\":\"slot\",\"value\":1}]},{\"type\":\"assignedTo\",\"resource\":{\"id\":\"#{lc2}\",\"href\":\"resourceInventoryManagement/v4/resource/card/#{lc2}\"},\"resourceRelationshipCharacteristic\":[{\"name\":\"slot\",\"value\":2}]}],\"resourceCharacteristic\":[{\"name\":\"shelf\",\"value\":{\"name\":\"QDONC-1001\",\"family\":\"ISAM\",\"model\":\"ISAM7330\",\"technology\":\"DSLAM\"}},{\"name\":\"slots\",\"value\":{\"first\":1,\"last\":10,\"free\":8,\"type\":\"LineCard\",\"algorithm\":\"lowest\"}}],\"place\":[{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) + ~s({\"id\":\"#{shelf.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{shelf.id}",\"category\":\"Network Resource\",\"name\":\"QDONC-0001\",\"resourceSpecification\":{\"id\":\"ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/ef016d85-9dbd-429c-84da-1df56cc7dda5\",\"name\":\"shelf\",\"version\":\"v1.0.0\"},\"resourceRelationship\":[{\"type\":\"assignedTo\",\"resource\":{\"id\":\"#{lc1}\",\"href\":\"resourceInventoryManagement/v4/resource/#{lc1}\"},\"resourceRelationshipCharacteristic\":[{\"name\":\"slot\",\"value\":1}]},{\"type\":\"assignedTo\",\"resource\":{\"id\":\"#{lc2}\",\"href\":\"resourceInventoryManagement/v4/resource/#{lc2}\"},\"resourceRelationshipCharacteristic\":[{\"name\":\"slot\",\"value\":2}]}],\"resourceCharacteristic\":[{\"name\":\"shelf\",\"value\":{\"name\":\"QDONC-1001\",\"family\":\"ISAM\",\"model\":\"ISAM7330\",\"technology\":\"DSLAM\"}},{\"name\":\"slots\",\"value\":{\"first\":1,\"last\":10,\"free\":8,\"type\":\"LineCard\",\"algorithm\":\"lowest\"}}],\"place\":[{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"Access\",\"name\":\"organizationId\",\"role\":\"Provider\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) end defp create_common_cards() do diff --git a/test/support/characteristics.ex b/test/support/characteristics.ex index 2ae4b42..b1d81bd 100644 --- a/test/support/characteristics.ex +++ b/test/support/characteristics.ex @@ -25,12 +25,13 @@ defmodule DiffoExample.Test.Characteristics do Enum.each( expected, fn {field, expected_value} -> - assert expected_value --- Map.get(characteristic.value, field) == nil + assert expected_value --- + (Diffo.Unwrap.unwrap(characteristic.value) |> Map.get(field)) == nil end ) true -> - assert expected --- characteristic.value == nil + assert expected --- Diffo.Unwrap.unwrap(characteristic.value) == nil end end ) From 33e57875b175b59cd55ff54f4033a2e4b56e6a55 Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Fri, 17 Apr 2026 20:57:54 +0930 Subject: [PATCH 05/13] update to dynamic 1 --- mix.lock | 2 +- test/access/characteristic_value_test.exs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index 990fffb..56ac9a9 100644 --- a/mix.lock +++ b/mix.lock @@ -8,7 +8,7 @@ "crux": {:hex, :crux, "0.1.2", "4441c9e3a34f1e340954ce96b9ad5a2de13ceb4f97b3f910211227bb92e2ca90", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "563ea3748ebfba9cc078e6d198a1d6a06015a8fae503f0b721363139f0ddb350"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, - "diffo": {:git, "https://github.com/diffo-dev/diffo.git", "9994aabd72cea1833d628195b9ef0fd097c4c885", [branch: "dev"]}, + "diffo": {:git, "https://github.com/diffo-dev/diffo.git", "9640dd460ce149e4cc4e3dd1308746ce7c465597", [branch: "dev"]}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, diff --git a/test/access/characteristic_value_test.exs b/test/access/characteristic_value_test.exs index 107ce72..8620106 100644 --- a/test/access/characteristic_value_test.exs +++ b/test/access/characteristic_value_test.exs @@ -33,7 +33,7 @@ defmodule DiffoExample.Access.CharacteristicValueTest do describe "DiffoExample.Access create Characteristics" do test "create characteristics" do - dslam_value = Value.dynamic(Dslam, Dslam.new!(%{name: @dslam, model: @model})) + dslam_value = Value.dynamic(Dslam.new!(%{name: @dslam, model: @model})) dslam = Diffo.Provider.create_characteristic!(%{ @@ -47,7 +47,7 @@ defmodule DiffoExample.Access.CharacteristicValueTest do assert encoding == ~s({\"name\":\"dslam\",\"value\":{\"name\":\"#{@dslam}\",\"family\":\"ISAM",\"model\":\"#{@model}\",\"technology\":\"eth\"}}) - aggregate_interface_value = Value.dynamic(AggregateInterface, + aggregate_interface_value = Value.dynamic( AggregateInterface.new!(%{ name: "F DONC BOXH 010J", physical_interface: "1000BASE-LX", @@ -69,7 +69,7 @@ defmodule DiffoExample.Access.CharacteristicValueTest do assert Jason.encode!(bandwidth_profile) == ~s({\"downstream\":24,\"upstream\":1,\"units\":\"Mbps\"}) - circuit_value = Value.dynamic(Circuit, + circuit_value = Value.dynamic( Circuit.new!%{ circuit_id: @circuit_id, cvlan_id: @cvlan_id, @@ -86,7 +86,7 @@ defmodule DiffoExample.Access.CharacteristicValueTest do assert Jason.encode!(circuit) == ~s({\"name\":\"circuit\",\"value\":{\"circuit_id\":\"#{@circuit_id}\",\"cvlan_id\":82,\"vci\":0,\"encapsulation\":\"IPoE\",\"bandwidth_profile\":{\"downstream\":24,\"upstream\":1,\"units\":\"Mbps\"}}}) - line_value = Value.dynamic(Line, + line_value = Value.dynamic( Line.new!(%{port: @port, slot: @slot, standard: :ADSL2plus, profile: @profile}) ) From 4e59dc0c3fc8588f75abc860819e79882b8a1746 Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Sat, 18 Apr 2026 16:13:07 +0930 Subject: [PATCH 06/13] update nbn domain for refactored diffo --- .../aggregate_interface.ex | 6 ++ .../services/characteristic_values/circuit.ex | 4 +- .../characteristic_values/constraints.ex | 1 + lib/nbn/resources/avc.ex | 2 +- .../characteristic_values/avc_value.ex | 4 +- .../characteristic_values/cvc_value.ex | 8 ++- .../characteristic_values/nni_value.ex | 1 + .../characteristic_values/ntd_value.ex | 4 +- .../characteristic_values/pri_value.ex | 1 + lib/nbn/resources/cvc.ex | 2 +- lib/nbn/resources/nbn_ethernet.ex | 29 ++++++-- lib/nbn/resources/types/bandwidth_profile.ex | 25 +++++-- lib/nbn/resources/types/speeds.ex | 37 +++++++--- lib/nbn/resources/types/technology.ex | 3 + lib/nbn/resources/uni.ex | 6 +- lib/nbn/util.ex | 36 ++++++---- mix.lock | 2 +- test/access/characteristic_value_test.exs | 39 ++++++----- test/access/dsl_access_test.exs | 6 +- test/nbn/nbn_ethernet_test.exs | 68 +++++++++++++------ test/support/characteristics.ex | 2 +- test/test_helper.exs | 2 +- 22 files changed, 202 insertions(+), 86 deletions(-) diff --git a/lib/access/services/characteristic_values/aggregate_interface.ex b/lib/access/services/characteristic_values/aggregate_interface.ex index b860c74..e733215 100644 --- a/lib/access/services/characteristic_values/aggregate_interface.ex +++ b/lib/access/services/characteristic_values/aggregate_interface.ex @@ -13,6 +13,12 @@ defmodule DiffoExample.Access.AggregateInterface do jason do pick [:name, :physical_interface, :physical_layer, :link_layer, :svlan_id, :vpi] compact(true) + + rename physical_interface: "physicalInterface", + physical_layer: "physicalLayer", + link_layer: "linkLayer", + svlan_id: "svlanId", + vpi: "VPI" end outstanding do diff --git a/lib/access/services/characteristic_values/circuit.ex b/lib/access/services/characteristic_values/circuit.ex index c56cacb..1488631 100644 --- a/lib/access/services/characteristic_values/circuit.ex +++ b/lib/access/services/characteristic_values/circuit.ex @@ -15,6 +15,7 @@ defmodule DiffoExample.Access.Circuit do jason do pick [:circuit_id, :cvlan_id, :vci, :encapsulation, :bandwidth_profile] compact(true) + rename circuit_id: "circuitId", vci: "VCI", bandwidth_profile: "bandwidthProfile" end outstanding do @@ -41,8 +42,7 @@ defmodule DiffoExample.Access.Circuit do constraints: [one_of: [:PPPoA, :PPPoE, :IPoE]], description: "the circuit encapsulation" - field :bandwidth_profile, BandwidthProfile, - description: "the circuit bandwidth profile" + field :bandwidth_profile, BandwidthProfile, description: "the circuit bandwidth profile" end defimpl String.Chars do diff --git a/lib/access/services/characteristic_values/constraints.ex b/lib/access/services/characteristic_values/constraints.ex index 092d6bc..2b023f1 100644 --- a/lib/access/services/characteristic_values/constraints.ex +++ b/lib/access/services/characteristic_values/constraints.ex @@ -13,6 +13,7 @@ defmodule DiffoExample.Access.Constraints do jason do pick [:max_latency, :min_profile] compact(true) + rename max_latency: "maxLatency", min_profile: "minProfile" end outstanding do diff --git a/lib/nbn/resources/avc.ex b/lib/nbn/resources/avc.ex index 5f36f8a..467d699 100644 --- a/lib/nbn/resources/avc.ex +++ b/lib/nbn/resources/avc.ex @@ -112,7 +112,7 @@ defmodule DiffoExample.Nbn.Avc do def mine_related(changeset, _context) when is_struct(changeset, Ash.Changeset) do reverse_relationships = Ash.Changeset.get_attribute(changeset, :reverse_relationships) - cvlan = {:cvlan, hd(hd(reverse_relationships).characteristics).value} + cvlan = {:cvlan, Diffo.Unwrap.unwrap(hd(hd(reverse_relationships).characteristics).value)} Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, avc: [cvlan]) end diff --git a/lib/nbn/resources/characteristic_values/avc_value.ex b/lib/nbn/resources/characteristic_values/avc_value.ex index 201d484..4060c1c 100644 --- a/lib/nbn/resources/characteristic_values/avc_value.ex +++ b/lib/nbn/resources/characteristic_values/avc_value.ex @@ -22,7 +22,9 @@ defmodule DiffoExample.Nbn.AvcValue do end typed_struct do - field :cvlan, :string, description: "the cvlan of the AVC, assigned by the related CVC" + field :cvlan, :integer, + constraints: [min: 0, max: 4000], + description: "the cvlan of the AVC, assigned by the related CVC" field :bandwidth_profile, BandwidthProfile, description: "the bandwidth profile of the AVC" end diff --git a/lib/nbn/resources/characteristic_values/cvc_value.ex b/lib/nbn/resources/characteristic_values/cvc_value.ex index e13e3d6..390d7c4 100644 --- a/lib/nbn/resources/characteristic_values/cvc_value.ex +++ b/lib/nbn/resources/characteristic_values/cvc_value.ex @@ -20,9 +20,13 @@ defmodule DiffoExample.Nbn.CvcValue do end typed_struct do - field :svlan, :string, description: "the svlan of the CVC, assigned by the related NNI Group" + field :svlan, :integer, + constraints: [min: 0, max: 4000], + description: "the svlan of the CVC, assigned by the related NNI Group" - field :bandwidth, :integer, description: "total CVC bandwidth in Mbps" + field :bandwidth, :integer, + constraints: [min: 0, max: 10000], + description: "total CVC bandwidth in Mbps" end defimpl String.Chars do diff --git a/lib/nbn/resources/characteristic_values/nni_value.ex b/lib/nbn/resources/characteristic_values/nni_value.ex index e3028fa..6135bf2 100644 --- a/lib/nbn/resources/characteristic_values/nni_value.ex +++ b/lib/nbn/resources/characteristic_values/nni_value.ex @@ -13,6 +13,7 @@ defmodule DiffoExample.Nbn.NniValue do jason do pick [:port_id, :capacity, :technology] compact(true) + rename port_id: "portId" end outstanding do diff --git a/lib/nbn/resources/characteristic_values/ntd_value.ex b/lib/nbn/resources/characteristic_values/ntd_value.ex index fa591a0..2ff219e 100644 --- a/lib/nbn/resources/characteristic_values/ntd_value.ex +++ b/lib/nbn/resources/characteristic_values/ntd_value.ex @@ -26,7 +26,9 @@ defmodule DiffoExample.Nbn.NtdValue do field :serial_number, :string, description: "the NTD serial number" - field :technology, Technology, description: "the access technology type", default: Technology.default + field :technology, Technology, + description: "the access technology type", + default: Technology.default() end defimpl String.Chars do diff --git a/lib/nbn/resources/characteristic_values/pri_value.ex b/lib/nbn/resources/characteristic_values/pri_value.ex index efe9a09..6c1bfb7 100644 --- a/lib/nbn/resources/characteristic_values/pri_value.ex +++ b/lib/nbn/resources/characteristic_values/pri_value.ex @@ -17,6 +17,7 @@ defmodule DiffoExample.Nbn.PriValue do jason do pick [:avcid, :uniid, :technology, :bandwidth_profile, :speeds] compact(true) + rename avcid: "AVCID", uniid: "UNIID", bandwidth_profile: "bandwidthProfile" end outstanding do diff --git a/lib/nbn/resources/cvc.ex b/lib/nbn/resources/cvc.ex index 9b21921..8e75dcf 100644 --- a/lib/nbn/resources/cvc.ex +++ b/lib/nbn/resources/cvc.ex @@ -127,7 +127,7 @@ defmodule DiffoExample.Nbn.Cvc do def mine_related(changeset, _context) when is_struct(changeset, Ash.Changeset) do reverse_relationships = Ash.Changeset.get_attribute(changeset, :reverse_relationships) - svlan = {:svlan, hd(hd(reverse_relationships).characteristics).value} + svlan = {:svlan, Diffo.Unwrap.unwrap(hd(hd(reverse_relationships).characteristics).value)} Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, cvc: [svlan]) end diff --git a/lib/nbn/resources/nbn_ethernet.ex b/lib/nbn/resources/nbn_ethernet.ex index ebb0413..57e3e49 100644 --- a/lib/nbn/resources/nbn_ethernet.ex +++ b/lib/nbn/resources/nbn_ethernet.ex @@ -119,22 +119,39 @@ defmodule DiffoExample.Nbn.NbnEthernet do Enum.reduce(forward_relationships, [], fn forward_relationship, acc -> {:ok, related} = Diffo.Provider.get_instance_by_id(forward_relationship.target_id) related_name = {alias_to_id(forward_relationship.alias), related.name} + case forward_relationship.alias do :uni -> # extract technology from uni characteristic - [{:technology, Util.extract(related.characteristics, :uni, :technology)} | [ related_name | acc]] + [ + {:technology, Util.extract(related.characteristics, :uni, :technology)} + | [related_name | acc] + ] + :avc -> # extract bandwidth_profile from avc characteristic - [{:bandwidth_profile, Util.extract(related.characteristics, :avc, :bandwidth_profile)} | [ related_name | acc]] + [ + {:bandwidth_profile, + Util.extract(related.characteristics, :avc, :bandwidth_profile)} + | [related_name | acc] + ] + _ -> - [ related_name | acc] + [related_name | acc] end end) # calculate the speeds from the extracted technology and bandwidth_profile - speeds = {:speeds, Util.speeds(Keyword.get(pri_updates, :bandwidth_profile), Keyword.get(pri_updates, :technology))} - - Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, pri: [speeds | pri_updates]) + speeds = + {:speeds, + Util.speeds( + Keyword.get(pri_updates, :bandwidth_profile), + Keyword.get(pri_updates, :technology) + )} + + Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, + pri: [speeds | pri_updates] + ) end defp alias_to_id(alias) when is_atom(alias) do diff --git a/lib/nbn/resources/types/bandwidth_profile.ex b/lib/nbn/resources/types/bandwidth_profile.ex index fdc6848..b9a11da 100644 --- a/lib/nbn/resources/types/bandwidth_profile.ex +++ b/lib/nbn/resources/types/bandwidth_profile.ex @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT defmodule DiffoExample.Nbn.BandwidthProfile do @moduledoc """ Diffo - TMF Service and Resource Management with a difference @@ -9,15 +12,29 @@ defmodule DiffoExample.Nbn.BandwidthProfile do use Ash.Type.NewType, subtype_of: :atom, - constraints: [one_of: bandwidth_profiles()] + constraints: [one_of: bandwidth_profiles()] def default do :home_fast end def bandwidth_profiles do - [:D12_U1, :D25_U5, :D25_U10, :D50_U20, :D100_U40, :D250_U100, :D500_U200, :D1000_U400, - :wireless_plus, :wireless_fast, :wireless_superfast, - :home_fast, :home_superfast, :home_ultrafast, :home_hyperfast] + [ + :D12_U1, + :D25_U5, + :D25_U10, + :D50_U20, + :D100_U40, + :D250_U100, + :D500_U200, + :D1000_U400, + :wireless_plus, + :wireless_fast, + :wireless_superfast, + :home_fast, + :home_superfast, + :home_ultrafast, + :home_hyperfast + ] end end diff --git a/lib/nbn/resources/types/speeds.ex b/lib/nbn/resources/types/speeds.ex index 36fea9c..01fae67 100644 --- a/lib/nbn/resources/types/speeds.ex +++ b/lib/nbn/resources/types/speeds.ex @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT defmodule DiffoExample.Nbn.Speeds do @moduledoc """ Diffo - TMF Service and Resource Management with a difference @@ -10,12 +13,19 @@ defmodule DiffoExample.Nbn.Speeds do use Ash.Type.NewType, subtype_of: :tuple, constraints: [ - fields: [downstream: [type: :integer], upstream: [type: :integer]] + fields: [downstream: [type: :integer], upstream: [type: :integer]] ] def speeds do [ - {12, 1}, {25, 5}, {25, 10}, {50, 20}, {100, 40}, {250, 100}, {500, 200}, {1000, 400}, + {12, 1}, + {25, 5}, + {25, 10}, + {50, 20}, + {100, 40}, + {250, 100}, + {500, 200}, + {1000, 400}, # :home_fast {500, 50}, # :home_superfast @@ -23,26 +33,35 @@ defmodule DiffoExample.Nbn.Speeds do # :home_ultrafast {1000, 100}, # :home_hyperfast - {2000, 100}, {2000, 200}, + {2000, 100}, + {2000, 200}, # :wireless_plus, :wireless_fast, :wireless_superfast - {100, 20}, {250, 20}, {400, 40} + {100, 20}, + {250, 20}, + {400, 40} ] end @impl true + def cast_input(nil, _constraints), do: {:ok, nil} + def cast_input(value, _constraints) when is_tuple(value) do - if value in speeds() do + if value in speeds() do {:ok, value} - else - {:error, "invalid downstream and upstream speed combination"} - end + else + {:error, "invalid downstream and upstream speed combination"} + end end def cast_input({_value, _constraints}), do: {:error, "value must be a tuple"} defimpl Jason.Encoder do def encode(speeds, _opts) do - Jason.OrderedObject.new(downstream: speeds.downstream, upstream: speeds.upstream, units: "Mbps") + Jason.OrderedObject.new( + downstream: speeds.downstream, + upstream: speeds.upstream, + units: "Mbps" + ) |> Jason.encode!() end end diff --git a/lib/nbn/resources/types/technology.ex b/lib/nbn/resources/types/technology.ex index c9d6003..6841c91 100644 --- a/lib/nbn/resources/types/technology.ex +++ b/lib/nbn/resources/types/technology.ex @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT defmodule DiffoExample.Nbn.Technology do @moduledoc """ Diffo - TMF Service and Resource Management with a difference diff --git a/lib/nbn/resources/uni.ex b/lib/nbn/resources/uni.ex index 3966828..0abd419 100644 --- a/lib/nbn/resources/uni.ex +++ b/lib/nbn/resources/uni.ex @@ -115,10 +115,12 @@ defmodule DiffoExample.Nbn.Uni do ntd_relationship = hd(reverse_relationships) - port = {:port, hd(ntd_relationship.characteristics).value} + port = {:port, Diffo.Unwrap.unwrap(hd(ntd_relationship.characteristics).value)} {:ok, ntd} = Diffo.Provider.get_instance_by_id(ntd_relationship.source_id) technology = {:technology, Util.extract(ntd.characteristics, :ntd, :technology)} - Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, uni: [port, technology]) + Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, + uni: [port, technology] + ) end end diff --git a/lib/nbn/util.ex b/lib/nbn/util.ex index 345041f..71c0a0d 100644 --- a/lib/nbn/util.ex +++ b/lib/nbn/util.ex @@ -41,27 +41,21 @@ defmodule DiffoExample.Nbn.Util do end @doc """ - Extracts a field value from a named item in a list + Extracts a field value from a named item value map in a list, each value map is unwrapped with Diffo.Unwrap protocol ## Examples iex> DiffoExample.Nbn.Util.extract([%{name: :avc, value: %{cvlan: 1}}], :avc, :cvlan) 1 """ def extract(items, name, field) when is_list(items) and is_atom(name) and is_atom(field) do - Enum.reduce_while(items, nil, fn item, acc -> - if name == item.name do - if item.value != nil do - {:halt, Map.get(item.value, field)} - else - {:halt, nil} - end - else - {:cont, acc} - end - end) + case Enum.find(items, &(&1.name == name)) do + nil -> nil + %{value: nil} -> nil + %{value: value} -> value |> Diffo.Unwrap.unwrap() |> Map.get(field) + end end - @doc""" + @doc """ Returns a tuple of maximum downstream and upstream speeds in Mbps given the bandwidth_profile and technology, or :error @@ -111,10 +105,13 @@ defmodule DiffoExample.Nbn.Util do case bandwidth_profile do :wireless_plus -> {100, 20} + :wireless_fast -> {250, 20} + :wireless_superfast -> {400, 40} + _ -> :error end @@ -124,14 +121,19 @@ defmodule DiffoExample.Nbn.Util do case bandwidth_profile do :home_fast -> {500, 50} + :home_superfast -> {750, 50} + :home_ultrafast -> {1000, 100} + :home_hyperfast -> {2000, 100} + :U100_D40 -> {100, 40} + _ -> :error end @@ -141,20 +143,28 @@ defmodule DiffoExample.Nbn.Util do case bandwidth_profile do :home_fast -> {500, 50} + :home_superfast -> {750, 50} + :home_ultrafast -> {1000, 100} + :home_hyperfast -> {2000, 200} + :D100_U40 -> {100, 40} + :D250_U100 -> {250, 100} + :D500_200 -> {500, 200} + :D1000_400 -> {1000, 400} + _ -> :error end diff --git a/mix.lock b/mix.lock index 56ac9a9..3a8d2e0 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.24.2", "38beca133e0dcab07e3c8a7c26e573287ada26e8ba8d4c90ac692b52b34b0309", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.6.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3fd2a99504c1f58290efc3382501369ee9070098784925bdd7df9dbea8611d32"}, + "ash": {:hex, :ash, "3.24.3", "f7280a43c5e64f769a450f3dd59ace6dcd73edcdd0de7599815b1b31f59292fb", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.6.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1022f8c549632137cbc8956f07bb4981405297f5abe7a752b4dffac175c3381"}, "ash_jason": {:hex, :ash_jason, "3.1.0", "84a88dfe5e25a20d55cf2d2664885cd086fa45871e8777aedc3ad96a282e2a6f", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:spark, ">= 2.1.21 and < 3.0.0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "71e6bbc421fb2cf7079f8804814145cca458116c839fc798f9606b806e07eb2b"}, "ash_neo4j": {:git, "https://github.com/diffo-dev/ash_neo4j.git", "044d9d123af30719a9f1f377e2c24b5cc8e21ea8", [branch: "dev"]}, "ash_outstanding": {:hex, :ash_outstanding, "0.2.4", "c72b91f1b8e4859fb033eddf66d0ba36cfd8af0c2a9748c7ef9e6ccfdb5d093d", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:outstanding, "~> 0.2.4", [hex: :outstanding, repo: "hexpm", optional: false]}], "hexpm", "64ba8f582ce69c9050352c75f0895db186c7a56f35039dab34c8e1ab7516f9ce"}, diff --git a/test/access/characteristic_value_test.exs b/test/access/characteristic_value_test.exs index 8620106..5ac058e 100644 --- a/test/access/characteristic_value_test.exs +++ b/test/access/characteristic_value_test.exs @@ -47,12 +47,14 @@ defmodule DiffoExample.Access.CharacteristicValueTest do assert encoding == ~s({\"name\":\"dslam\",\"value\":{\"name\":\"#{@dslam}\",\"family\":\"ISAM",\"model\":\"#{@model}\",\"technology\":\"eth\"}}) - aggregate_interface_value = Value.dynamic( - AggregateInterface.new!(%{ - name: "F DONC BOXH 010J", - physical_interface: "1000BASE-LX", - svlan_id: @svlan_id - })) + aggregate_interface_value = + Value.dynamic( + AggregateInterface.new!(%{ + name: "F DONC BOXH 010J", + physical_interface: "1000BASE-LX", + svlan_id: @svlan_id + }) + ) aggregate_interface = Diffo.Provider.create_characteristic!(%{ @@ -62,19 +64,21 @@ defmodule DiffoExample.Access.CharacteristicValueTest do }) assert Jason.encode!(aggregate_interface) == - ~s({\"name\":\"aggregate_interface\",\"value\":{\"name\":\"F DONC BOXH 010J\",\"physical_interface\":\"1000BASE-LX\",\"physical_layer\":\"GbE\",\"link_layer\":\"QinQ\",\"svlan_id\":3108,\"vpi\":0}}) + ~s({\"name\":\"aggregate_interface\",\"value\":{\"name\":\"F DONC BOXH 010J\",\"physicalInterface\":\"1000BASE-LX\",\"physicalLayer\":\"GbE\",\"linkLayer\":\"QinQ\",\"svlanId\":3108,\"VPI\":0}}) bandwidth_profile = BandwidthProfile.new!(%{downstream: 24, upstream: 1}) assert Jason.encode!(bandwidth_profile) == ~s({\"downstream\":24,\"upstream\":1,\"units\":\"Mbps\"}) - circuit_value = Value.dynamic( - Circuit.new!%{ - circuit_id: @circuit_id, - cvlan_id: @cvlan_id, - bandwidth_profile: bandwidth_profile - }) + circuit_value = + Value.dynamic( + Circuit.new!(%{ + circuit_id: @circuit_id, + cvlan_id: @cvlan_id, + bandwidth_profile: bandwidth_profile + }) + ) circuit = Diffo.Provider.create_characteristic!(%{ @@ -84,11 +88,12 @@ defmodule DiffoExample.Access.CharacteristicValueTest do }) assert Jason.encode!(circuit) == - ~s({\"name\":\"circuit\",\"value\":{\"circuit_id\":\"#{@circuit_id}\",\"cvlan_id\":82,\"vci\":0,\"encapsulation\":\"IPoE\",\"bandwidth_profile\":{\"downstream\":24,\"upstream\":1,\"units\":\"Mbps\"}}}) + ~s({\"name\":\"circuit\",\"value\":{\"circuitId\":\"#{@circuit_id}\",\"cvlan_id\":82,\"VCI\":0,\"encapsulation\":\"IPoE\",\"bandwidthProfile\":{\"downstream\":24,\"upstream\":1,\"units\":\"Mbps\"}}}) - line_value = Value.dynamic( - Line.new!(%{port: @port, slot: @slot, standard: :ADSL2plus, profile: @profile}) - ) + line_value = + Value.dynamic( + Line.new!(%{port: @port, slot: @slot, standard: :ADSL2plus, profile: @profile}) + ) line = Diffo.Provider.create_characteristic!(%{ diff --git a/test/access/dsl_access_test.exs b/test/access/dsl_access_test.exs index 41a7e41..58818f7 100644 --- a/test/access/dsl_access_test.exs +++ b/test/access/dsl_access_test.exs @@ -106,7 +106,7 @@ defmodule DiffoExample.Access.DslAccessTest do encoding = Jason.encode!(dsl_access) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{dsl_access.id}",\"href\":\"serviceInventoryManagement/v4/service/#{dsl_access.id}\",\"category\":\"Network Service\",\"serviceSpecification\":{\"id\":\"da9b207a-26c3-451d-8abd-0640c6349979\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/da9b207a-26c3-451d-8abd-0640c6349979\",\"name\":\"dslAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"initial\",\"feature\":[{\"name\":\"dynamic_line_management\",\"isEnabled\":true,\"featureCharacteristic\":[{\"name\":\"constraints\",\"value\":{}}]}],\"serviceCharacteristic\":[{\"name\":\"aggregate_interface\",\"value\":{\"physical_layer\":\"GbE\",\"link_layer\":\"QinQ\",\"svlan_id\":0,\"vpi\":0}},{\"name\":\"circuit\",\"value\":{\"cvlan_id\":0,\"vci\":0,\"encapsulation\":\"IPoE\"}},{\"name\":\"dslam\",\"value\":{\"family\":\"ISAM\",\"technology\":\"eth\"}},{\"name\":\"line\",\"value\":{\"standard\":\"ADSL2plus\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"IND000000897354\",\"name\":\"individualId\",\"role\":\"Customer\",\"@referredType\":\"Individual\",\"@type\":\"PartyRef\"},{\"id\":\"ORG000000123456\",\"name\":\"organizationId\",\"role\":\"Reseller\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) + ~s({\"id\":\"#{dsl_access.id}",\"href\":\"serviceInventoryManagement/v4/service/#{dsl_access.id}\",\"category\":\"Network Service\",\"serviceSpecification\":{\"id\":\"da9b207a-26c3-451d-8abd-0640c6349979\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/da9b207a-26c3-451d-8abd-0640c6349979\",\"name\":\"dslAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"initial\",\"feature\":[{\"name\":\"dynamic_line_management\",\"isEnabled\":true,\"featureCharacteristic\":[{\"name\":\"constraints\",\"value\":{}}]}],\"serviceCharacteristic\":[{\"name\":\"aggregate_interface\",\"value\":{\"physicalLayer\":\"GbE\",\"linkLayer\":\"QinQ\",\"svlanId\":0,\"VPI\":0}},{\"name\":\"circuit\",\"value\":{\"cvlan_id\":0,\"VCI\":0,\"encapsulation\":\"IPoE\"}},{\"name\":\"dslam\",\"value\":{\"family\":\"ISAM\",\"technology\":\"eth\"}},{\"name\":\"line\",\"value\":{\"standard\":\"ADSL2plus\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"IND000000897354\",\"name\":\"individualId\",\"role\":\"Customer\",\"@referredType\":\"Individual\",\"@type\":\"PartyRef\"},{\"id\":\"ORG000000123456\",\"name\":\"organizationId\",\"role\":\"Reseller\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) end test "advance service to feasibilityChecked" do @@ -134,7 +134,7 @@ defmodule DiffoExample.Access.DslAccessTest do encoding = Jason.encode!(dsl_access) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{dsl_access.id}",\"href\":\"serviceInventoryManagement/v4/service/#{dsl_access.id}\",\"category\":\"Network Service\",\"serviceSpecification\":{\"id\":\"da9b207a-26c3-451d-8abd-0640c6349979\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/da9b207a-26c3-451d-8abd-0640c6349979\",\"name\":\"dslAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"feasibilityChecked\",\"operatingStatus\":\"feasible\",\"feature\":[{\"name\":\"dynamic_line_management\",\"isEnabled\":true,\"featureCharacteristic\":[{\"name\":\"constraints\",\"value\":{}}]}],\"serviceCharacteristic\":[{\"name\":\"aggregate_interface\",\"value\":{\"physical_layer\":\"GbE\",\"link_layer\":\"QinQ\",\"svlan_id\":0,\"vpi\":0}},{\"name\":\"circuit\",\"value\":{\"cvlan_id\":0,\"vci\":0,\"encapsulation\":\"IPoE\"}},{\"name\":\"dslam\",\"value\":{\"family\":\"ISAM\",\"technology\":\"eth\"}},{\"name\":\"line\",\"value\":{\"standard\":\"ADSL2plus\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"IND000000897354\",\"name\":\"individualId\",\"role\":\"Customer\",\"@referredType\":\"Individual\",\"@type\":\"PartyRef\"},{\"id\":\"ORG000000123456\",\"name\":\"organizationId\",\"role\":\"Reseller\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) + ~s({\"id\":\"#{dsl_access.id}",\"href\":\"serviceInventoryManagement/v4/service/#{dsl_access.id}\",\"category\":\"Network Service\",\"serviceSpecification\":{\"id\":\"da9b207a-26c3-451d-8abd-0640c6349979\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/da9b207a-26c3-451d-8abd-0640c6349979\",\"name\":\"dslAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"feasibilityChecked\",\"operatingStatus\":\"feasible\",\"feature\":[{\"name\":\"dynamic_line_management\",\"isEnabled\":true,\"featureCharacteristic\":[{\"name\":\"constraints\",\"value\":{}}]}],\"serviceCharacteristic\":[{\"name\":\"aggregate_interface\",\"value\":{\"physicalLayer\":\"GbE\",\"linkLayer\":\"QinQ\",\"svlanId\":0,\"VPI\":0}},{\"name\":\"circuit\",\"value\":{\"cvlan_id\":0,\"VCI\":0,\"encapsulation\":\"IPoE\"}},{\"name\":\"dslam\",\"value\":{\"family\":\"ISAM\",\"technology\":\"eth\"}},{\"name\":\"line\",\"value\":{\"standard\":\"ADSL2plus\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"IND000000897354\",\"name\":\"individualId\",\"role\":\"Customer\",\"@referredType\":\"Individual\",\"@type\":\"PartyRef\"},{\"id\":\"ORG000000123456\",\"name\":\"organizationId\",\"role\":\"Reseller\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) end end @@ -175,7 +175,7 @@ defmodule DiffoExample.Access.DslAccessTest do encoding = Jason.encode!(dsl_access) |> Diffo.Util.summarise_dates() assert encoding == - ~s({\"id\":\"#{dsl_access.id}",\"href\":\"serviceInventoryManagement/v4/service/#{dsl_access.id}\",\"category\":\"Network Service\",\"serviceSpecification\":{\"id\":\"da9b207a-26c3-451d-8abd-0640c6349979\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/da9b207a-26c3-451d-8abd-0640c6349979\",\"name\":\"dslAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"reserved\",\"operatingStatus\":\"feasible\",\"feature\":[{\"name\":\"dynamic_line_management\",\"isEnabled\":true,\"featureCharacteristic\":[{\"name\":\"constraints\",\"value\":{}}]}],\"serviceCharacteristic\":[{\"name\":\"aggregate_interface\",\"value\":{\"name\":\"eth0\",\"physical_layer\":\"GbE\",\"link_layer\":\"QinQ\",\"svlan_id\":3108,\"vpi\":0}},{\"name\":\"circuit\",\"value\":{\"cvlan_id\":82,\"vci\":0,\"encapsulation\":\"IPoE\"}},{\"name\":\"dslam\",\"value\":{\"name\":\"QDONC0001\",\"family\":\"ISAM\",\"model\":\"ISAM7330\",\"technology\":\"eth\"}},{\"name\":\"line\",\"value\":{\"port\":5,\"slot\":10,\"standard\":\"ADSL2plus\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"IND000000897354\",\"name\":\"individualId\",\"role\":\"Customer\",\"@referredType\":\"Individual\",\"@type\":\"PartyRef\"},{\"id\":\"ORG000000123456\",\"name\":\"organizationId\",\"role\":\"Reseller\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) + ~s({\"id\":\"#{dsl_access.id}",\"href\":\"serviceInventoryManagement/v4/service/#{dsl_access.id}\",\"category\":\"Network Service\",\"serviceSpecification\":{\"id\":\"da9b207a-26c3-451d-8abd-0640c6349979\",\"href\":\"serviceCatalogManagement/v4/serviceSpecification/da9b207a-26c3-451d-8abd-0640c6349979\",\"name\":\"dslAccess\",\"version\":\"v1.0.0\"},\"serviceDate\":\"now\",\"state\":\"reserved\",\"operatingStatus\":\"feasible\",\"feature\":[{\"name\":\"dynamic_line_management\",\"isEnabled\":true,\"featureCharacteristic\":[{\"name\":\"constraints\",\"value\":{}}]}],\"serviceCharacteristic\":[{\"name\":\"aggregate_interface\",\"value\":{\"name\":\"eth0\",\"physicalLayer\":\"GbE\",\"linkLayer\":\"QinQ\",\"svlanId\":3108,\"VPI\":0}},{\"name\":\"circuit\",\"value\":{\"cvlan_id\":82,\"VCI\":0,\"encapsulation\":\"IPoE\"}},{\"name\":\"dslam\",\"value\":{\"name\":\"QDONC0001\",\"family\":\"ISAM\",\"model\":\"ISAM7330\",\"technology\":\"eth\"}},{\"name\":\"line\",\"value\":{\"port\":5,\"slot\":10,\"standard\":\"ADSL2plus\"}}],\"place\":[{\"id\":\"1657363\",\"href\":\"place/telco/1657363\",\"name\":\"addressId\",\"role\":\"CustomerSite\",\"@referredType\":\"GeographicAddress\",\"@type\":\"PlaceRef\"},{\"id\":\"DONC-0001\",\"href\":\"place/telco/DONC-0001\",\"name\":\"esaId\",\"role\":\"ServingArea\",\"@referredType\":\"GeographicLocation\",\"@type\":\"PlaceRef\"}],\"relatedParty\":[{\"id\":\"IND000000897354\",\"name\":\"individualId\",\"role\":\"Customer\",\"@referredType\":\"Individual\",\"@type\":\"PartyRef\"},{\"id\":\"ORG000000123456\",\"name\":\"organizationId\",\"role\":\"Reseller\",\"@referredType\":\"Organization\",\"@type\":\"PartyRef\"}]}) end end diff --git a/test/nbn/nbn_ethernet_test.exs b/test/nbn/nbn_ethernet_test.exs index ba5899f..86abbf8 100644 --- a/test/nbn/nbn_ethernet_test.exs +++ b/test/nbn/nbn_ethernet_test.exs @@ -69,14 +69,19 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do encoding = Jason.encode!(access) |> Diffo.Util.summarise_dates() assert encoding == - ~s({"id":"#{access.id}","href":"resourceInventoryManagement/v4/resource/nbnEthernet/#{access.id}","category":"Network Resource",\"name\":\"#{access.name}","resourceSpecification":{"id":"f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","href":"resourceCatalogManagement/v4/resourceSpecification/f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","name":"nbnEthernet","version":"v1.0.0"},"resourceCharacteristic":[{"name":"pri","value":{}}]}) + ~s({"id":"#{access.id}","href":"resourceInventoryManagement/v4/resource/#{access.id}","category":"Network Resource",\"name\":\"#{access.name}","resourceSpecification":{"id":"f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","href":"resourceCatalogManagement/v4/resourceSpecification/f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","name":"nbnEthernet","version":"v1.0.0"},"resourceCharacteristic":[{"name":"pri","value":{}}]}) end test "define nbn_ethernet access" do {:ok, access} = Nbn.build_nbn_ethernet(%{}) updates = [ - pri: [avcid: "AVC000910202941", uniid: "UNI000302814545", speed: 1000, technology: :FTTP] + pri: [ + avcid: "AVC000910202941", + uniid: "UNI000302814545", + speeds: {500, 50}, + technology: :FTTP + ] ] {:ok, access} = Nbn.define_nbn_ethernet(access, %{characteristic_value_updates: updates}) @@ -86,7 +91,7 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do pri: [ avcid: "AVC000910202941", uniid: "UNI000302814545", - speed: 1000, + speeds: {500, 50}, technology: :FTTP ] ], @@ -94,26 +99,47 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do ) end - @tag debug: true test "relate nbn_ethernet" do {:ok, access} = Nbn.build_nbn_ethernet(%{}) {:ok, nni_group} = Nbn.build_nni_group(%{}) {:ok, cvc} = Nbn.build_cvc(%{}) - {:ok, _nni_group} = Nbn.assign_svlan(nni_group, %{assignment: %Assignment{assignee_id: cvc.id, operation: :auto_assign}}) + + {:ok, _nni_group} = + Nbn.assign_svlan(nni_group, %{ + assignment: %Assignment{assignee_id: cvc.id, operation: :auto_assign} + }) + {:ok, cvc} = Nbn.get_cvc_by_id(cvc.id, load: [:reverse_relationships]) {:ok, cvc} = Nbn.mine_cvc(cvc) {:ok, avc} = Nbn.build_avc(%{}) - {:ok, avc} = Nbn.define_avc(avc, %{characteristic_value_updates: [avc: [bandwidth_profile: :home_fast]]}) - {:ok, _cvc} = Nbn.assign_cvlan(cvc, %{assignment: %Assignment{assignee_id: avc.id, operation: :auto_assign}}) + + {:ok, avc} = + Nbn.define_avc(avc, %{ + characteristic_value_updates: [avc: [bandwidth_profile: :home_fast]] + }) + + {:ok, _cvc} = + Nbn.assign_cvlan(cvc, %{ + assignment: %Assignment{assignee_id: avc.id, operation: :auto_assign} + }) + {:ok, avc} = Nbn.get_avc_by_id(avc.id, load: [:reverse_relationships]) {:ok, avc} = Nbn.mine_avc(avc) {:ok, ntd} = Nbn.build_ntd(%{}) - {:ok, ntd} = Nbn.define_ntd(ntd, %{characteristic_value_updates: [ntd: [technology: :FTTP]]}) + + {:ok, ntd} = + Nbn.define_ntd(ntd, %{characteristic_value_updates: [ntd: [technology: :FTTP]]}) + {:ok, uni} = Nbn.build_uni(%{}) - {:ok, _ntd} = Nbn.assign_port(ntd, %{assignment: %Assignment{assignee_id: uni.id, operation: :auto_assign}}) + + {:ok, _ntd} = + Nbn.assign_port(ntd, %{ + assignment: %Assignment{assignee_id: uni.id, operation: :auto_assign} + }) + {:ok, uni} = Nbn.get_uni_by_id(uni.id, load: [:reverse_relationships]) {:ok, uni} = Nbn.mine_uni(uni) @@ -129,7 +155,7 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do encoding = Jason.encode!(access) |> Diffo.Util.summarise_dates() assert encoding == - ~s({"id":"#{access.id}","href":"resourceInventoryManagement/v4/resource/nbnEthernet/#{access.id}","category":"Network Resource","name":"#{access.name}","resourceSpecification":{"id":"f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","href":"resourceCatalogManagement/v4/resourceSpecification/f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","name":"nbnEthernet","version":"v1.0.0"},"resourceRelationship":[{"alias":"avc","type":"owns","resource":{"id":"#{avc.id}","href":"resourceInventoryManagement/v4/resource/avc/#{avc.id}"}},{"alias":"uni","type":"owns","resource":{"id\":"#{uni.id}","href":"resourceInventoryManagement/v4/resource/uni/#{uni.id}"}}],"supportingResource":[{"id":"avc","href":"resourceInventoryManagement/v4/resource/avc/#{avc.id}"},{"id\":"uni","href":"resourceInventoryManagement/v4/resource/uni/#{uni.id}"}],"resourceCharacteristic":[{"name":"pri","value":{"avcid":"#{avc.name}","uniid":"#{uni.name}","technology":"FTTP","bandwidth_profile":"home_fast","speeds":[500,50]}}]}) + ~s({"id":"#{access.id}","href":"resourceInventoryManagement/v4/resource/#{access.id}","category":"Network Resource","name":"#{access.name}","resourceSpecification":{"id":"f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","href":"resourceCatalogManagement/v4/resourceSpecification/f2a4c6e8-1b3d-4f5a-8c7e-9d0b2e4f6a8c","name":"nbnEthernet","version":"v1.0.0"},"resourceRelationship":[{"alias":"avc","type":"owns","resource":{"id":"#{avc.id}","href":"resourceInventoryManagement/v4/resource/#{avc.id}"}},{"alias":"uni","type":"owns","resource":{"id\":"#{uni.id}","href":"resourceInventoryManagement/v4/resource/#{uni.id}"}}],"supportingResource":[{"id":"avc","href":"resourceInventoryManagement/v4/resource/#{avc.id}"},{"id\":"uni","href":"resourceInventoryManagement/v4/resource/#{uni.id}"}],"resourceCharacteristic":[{"name":"pri","value":{"AVCID":"#{avc.name}","UNIID":"#{uni.name}","technology":"FTTP","bandwidthProfile":"home_fast","speeds":[500,50]}}]}) end end @@ -148,13 +174,13 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do {:ok, uni} = Nbn.build_uni(%{}) updates = [ - uni: [vlan_id: 101, bandwidth_profile: "TC4", technology: :FTTP] + uni: [port: 1, encapsulation: "DSCP Mapped", technology: :FTTP] ] {:ok, uni} = Nbn.define_uni(uni, %{characteristic_value_updates: updates}) Characteristics.check_values( - [uni: [vlan_id: 101, bandwidth_profile: "TC4", technology: :FTTP]], + [uni: [port: 1, encapsulation: "DSCP Mapped", technology: :FTTP]], uni ) end @@ -200,7 +226,7 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do updates = [ ntd: [model: "Sercomm CG4000A", serial_number: "SCOMA1A057A2", technology: :FTTP], - ports: [first: 1, last: 4, free: 4, type: "port"] + ports: [first: 1, last: 4, free: 4, assignable_type: "port"] ] {:ok, ntd} = Nbn.define_ntd(ntd, %{characteristic_value_updates: updates}) @@ -208,7 +234,7 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do Characteristics.check_values( [ ntd: [model: "Sercomm CG4000A", serial_number: "SCOMA1A057A2", technology: :FTTP], - ports: [first: 1, last: 4, free: 4, type: "port"] + ports: [first: 1, last: 4, free: 4, assignable_type: "port"] ], ntd ) @@ -219,7 +245,7 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do Characteristics.check_values( [ ntd: [model: "Sercomm CG4000A", serial_number: "SCOMA1A057A2", technology: :FTTP], - ports: [first: 1, last: 4, free: 2, type: "port"] + ports: [first: 1, last: 4, free: 2, assignable_type: "port"] ], ntd ) @@ -255,7 +281,7 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do updates = [ cvc: [svlan: 1, bandwidth: 10000], - cvlans: [first: 1, last: 4000, free: 4000, type: "cvlan"] + cvlans: [first: 1, last: 4000, free: 4000, assignable_type: "cvlan"] ] {:ok, cvc} = Nbn.define_cvc(cvc, %{characteristic_value_updates: updates}) @@ -263,7 +289,7 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do Characteristics.check_values( [ cvc: [svlan: 1, bandwidth: 10000], - cvlans: [first: 1, last: 4000, free: 4000, type: "cvlan"] + cvlans: [first: 1, last: 4000, free: 4000, assignable_type: "cvlan"] ], cvc ) @@ -274,7 +300,7 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do Characteristics.check_values( [ cvc: [svlan: 1, bandwidth: 10000], - cvlans: [first: 1, last: 4000, free: 3998, type: "cvlan"] + cvlans: [first: 1, last: 4000, free: 3998, assignable_type: "cvlan"] ], cvc ) @@ -311,7 +337,7 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do updates = [ nni_group: [name: "SYD-POI-01", location: "Sydney Olympic Park"], - svlans: [first: 1, last: 4000, free: 4000, type: "svlan"] + svlans: [first: 1, last: 4000, free: 4000, assignable_type: "svlan"] ] {:ok, nni_group} = @@ -320,7 +346,7 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do Characteristics.check_values( [ nni_group: [name: "SYD-POI-01", location: "Sydney Olympic Park"], - svlans: [first: 1, last: 4000, free: 4000, type: "svlan"] + svlans: [first: 1, last: 4000, free: 4000, assignable_type: "svlan"] ], nni_group ) @@ -331,7 +357,7 @@ defmodule DiffoExample.Nbn.NbnEthernetTest do Characteristics.check_values( [ nni_group: [name: "SYD-POI-01", location: "Sydney Olympic Park"], - svlans: [first: 1, last: 4000, free: 3998, type: "svlan"] + svlans: [first: 1, last: 4000, free: 3998, assignable_type: "svlan"] ], nni_group ) diff --git a/test/support/characteristics.ex b/test/support/characteristics.ex index 519c233..00bed31 100644 --- a/test/support/characteristics.ex +++ b/test/support/characteristics.ex @@ -11,7 +11,7 @@ defmodule DiffoExample.Test.Characteristics do import Outstand import ExUnit.Assertions - @doc""" + @doc """ uses Outstanding to check expected values within instance characteristics """ def check_values(expected_values, instance) diff --git a/test/test_helper.exs b/test/test_helper.exs index 1caa23b..35444cb 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -6,4 +6,4 @@ Mix.Task.run("app.start") ExUnit.start() level = Application.get_env(:logger, :console) |> Keyword.get(:level) Logger.put_application_level(:diffo, level) -Logger.put_application_level(:ash_neo4j, level) +Logger.put_application_level(:ash_neo4j, :error) From df7fed154057865ca199e0889901b95d00e86c13 Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Sat, 18 Apr 2026 16:55:07 +0930 Subject: [PATCH 07/13] speeds and technology now Enum --- lib/nbn/resources/nbn_ethernet.ex | 3 +- lib/nbn/resources/types/bandwidth_profile.ex | 17 +-- lib/nbn/resources/types/speeds.ex | 120 ++++++++++++++++++ lib/nbn/resources/types/technology.ex | 11 +- lib/nbn/util.ex | 121 ------------------- test/diffo_example_test.exs | 1 + 6 files changed, 129 insertions(+), 144 deletions(-) diff --git a/lib/nbn/resources/nbn_ethernet.ex b/lib/nbn/resources/nbn_ethernet.ex index 57e3e49..d4594a5 100644 --- a/lib/nbn/resources/nbn_ethernet.ex +++ b/lib/nbn/resources/nbn_ethernet.ex @@ -18,6 +18,7 @@ defmodule DiffoExample.Nbn.NbnEthernet do alias DiffoExample.Nbn alias DiffoExample.Nbn.Util + alias DiffoExample.Nbn.Speeds use Ash.Resource, fragments: [BaseInstance], @@ -144,7 +145,7 @@ defmodule DiffoExample.Nbn.NbnEthernet do # calculate the speeds from the extracted technology and bandwidth_profile speeds = {:speeds, - Util.speeds( + Speeds.speeds( Keyword.get(pri_updates, :bandwidth_profile), Keyword.get(pri_updates, :technology) )} diff --git a/lib/nbn/resources/types/bandwidth_profile.ex b/lib/nbn/resources/types/bandwidth_profile.ex index b9a11da..0f30dea 100644 --- a/lib/nbn/resources/types/bandwidth_profile.ex +++ b/lib/nbn/resources/types/bandwidth_profile.ex @@ -8,18 +8,8 @@ defmodule DiffoExample.Nbn.BandwidthProfile do BandwidthProfile type for NBN domain """ - require Ash.Type.NewType - - use Ash.Type.NewType, - subtype_of: :atom, - constraints: [one_of: bandwidth_profiles()] - - def default do - :home_fast - end - - def bandwidth_profiles do - [ + use Ash.Type.Enum, + values: [ :D12_U1, :D25_U5, :D25_U10, @@ -36,5 +26,6 @@ defmodule DiffoExample.Nbn.BandwidthProfile do :home_ultrafast, :home_hyperfast ] - end + + def default, do: :home_fast end diff --git a/lib/nbn/resources/types/speeds.ex b/lib/nbn/resources/types/speeds.ex index 01fae67..ce3a931 100644 --- a/lib/nbn/resources/types/speeds.ex +++ b/lib/nbn/resources/types/speeds.ex @@ -9,6 +9,7 @@ defmodule DiffoExample.Nbn.Speeds do """ require Ash.Type.NewType + alias DiffoExample.Nbn.Technology use Ash.Type.NewType, subtype_of: :tuple, @@ -65,4 +66,123 @@ defmodule DiffoExample.Nbn.Speeds do |> Jason.encode!() end end + + @doc """ + Returns a tuple of maximum downstream and upstream speeds in Mbps + given the bandwidth_profile and technology, or :error + + ## Examples + iex> DiffoExample.Nbn.Speeds.speeds(:D12_U1, :Satellite) + {12, 1} + iex> DiffoExample.Nbn.Speeds.speeds(:home_fast, :FTTP) + {500, 50} + iex> DiffoExample.Nbn.Speeds.speeds(:home_hyperfast, :HFC) + {2000, 100} + iex> DiffoExample.Nbn.Speeds.speeds(:home_fast, :FixedWireless) + :error + """ + def speeds(:D12_U1, technology) when is_atom(technology) do + if technology in Technology.values() do + {12, 1} + else + :error + end + end + + def speeds(:D25_U5, technology) when is_atom(technology) do + if technology in Technology.values() do + {25, 5} + else + :error + end + end + + def speeds(:D25_U10, technology) when is_atom(technology) do + if technology in [:FTTP, :HFC, :FTTC] do + {25, 10} + else + :error + end + end + + def speeds(:D50_U20, technology) when is_atom(technology) do + if technology in [:FTTP, :HFC, :FTTC] do + {50, 20} + else + :error + end + end + + def speeds(bandwidth_profile, :FixedWireless) do + case bandwidth_profile do + :wireless_plus -> + {100, 20} + + :wireless_fast -> + {250, 20} + + :wireless_superfast -> + {400, 40} + + _ -> + :error + end + end + + def speeds(bandwidth_profile, :HFC) do + case bandwidth_profile do + :home_fast -> + {500, 50} + + :home_superfast -> + {750, 50} + + :home_ultrafast -> + {1000, 100} + + :home_hyperfast -> + {2000, 100} + + :U100_D40 -> + {100, 40} + + _ -> + :error + end + end + + def speeds(bandwidth_profile, :FTTP) do + case bandwidth_profile do + :home_fast -> + {500, 50} + + :home_superfast -> + {750, 50} + + :home_ultrafast -> + {1000, 100} + + :home_hyperfast -> + {2000, 200} + + :D100_U40 -> + {100, 40} + + :D250_U100 -> + {250, 100} + + :D500_200 -> + {500, 200} + + :D1000_400 -> + {1000, 400} + + _ -> + :error + end + end + + def speeds(_bandwidth, _technology) do + :error + end end diff --git a/lib/nbn/resources/types/technology.ex b/lib/nbn/resources/types/technology.ex index 6841c91..74f7b6a 100644 --- a/lib/nbn/resources/types/technology.ex +++ b/lib/nbn/resources/types/technology.ex @@ -8,17 +8,10 @@ defmodule DiffoExample.Nbn.Technology do Technology type for NBN domain """ - require Ash.Type.NewType - - use Ash.Type.NewType, - subtype_of: :atom, - constraints: [one_of: technology()] + use Ash.Type.Enum, + values: [:FTTP, :FTTN, :FTTB, :FTTC, :HFC, :FixedWireless, :Satellite] def default do :FTTP end - - def technology do - [:FTTP, :FTTN, :FTTB, :FTTC, :HFC, :FixedWireless, :Satellite] - end end diff --git a/lib/nbn/util.ex b/lib/nbn/util.ex index 71c0a0d..453bcb7 100644 --- a/lib/nbn/util.ex +++ b/lib/nbn/util.ex @@ -9,8 +9,6 @@ defmodule DiffoExample.Nbn.Util do Util - various utilities for NBN domain """ - alias DiffoExample.Nbn.Technology - @doc """ Generates a new random NBN identifier with the prefix @@ -54,123 +52,4 @@ defmodule DiffoExample.Nbn.Util do %{value: value} -> value |> Diffo.Unwrap.unwrap() |> Map.get(field) end end - - @doc """ - Returns a tuple of maximum downstream and upstream speeds in Mbps - given the bandwidth_profile and technology, or :error - - ## Examples - iex> DiffoExample.Nbn.Util.speeds(:D12_U1, :Satellite) - {12, 1} - iex> DiffoExample.Nbn.Util.speeds(:home_fast, :FTTP) - {500, 50} - iex> DiffoExample.Nbn.Util.speeds(:home_hyperfast, :HFC) - {2000, 100} - iex> DiffoExample.Nbn.Util.speeds(:home_fast, :FixedWireless) - :error - """ - def speeds(:D12_U1, technology) when is_atom(technology) do - if technology in Technology.technology() do - {12, 1} - else - :error - end - end - - def speeds(:D25_U5, technology) when is_atom(technology) do - if technology in Technology.technology() do - {25, 5} - else - :error - end - end - - def speeds(:D25_U10, technology) when is_atom(technology) do - if technology in [:FTTP, :HFC, :FTTC] do - {25, 10} - else - :error - end - end - - def speeds(:D50_U20, technology) when is_atom(technology) do - if technology in [:FTTP, :HFC, :FTTC] do - {50, 20} - else - :error - end - end - - def speeds(bandwidth_profile, :FixedWireless) do - case bandwidth_profile do - :wireless_plus -> - {100, 20} - - :wireless_fast -> - {250, 20} - - :wireless_superfast -> - {400, 40} - - _ -> - :error - end - end - - def speeds(bandwidth_profile, :HFC) do - case bandwidth_profile do - :home_fast -> - {500, 50} - - :home_superfast -> - {750, 50} - - :home_ultrafast -> - {1000, 100} - - :home_hyperfast -> - {2000, 100} - - :U100_D40 -> - {100, 40} - - _ -> - :error - end - end - - def speeds(bandwidth_profile, :FTTP) do - case bandwidth_profile do - :home_fast -> - {500, 50} - - :home_superfast -> - {750, 50} - - :home_ultrafast -> - {1000, 100} - - :home_hyperfast -> - {2000, 200} - - :D100_U40 -> - {100, 40} - - :D250_U100 -> - {250, 100} - - :D500_200 -> - {500, 200} - - :D1000_400 -> - {1000, 400} - - _ -> - :error - end - end - - def speed(_bandwidth, _technology) do - :error - end end diff --git a/test/diffo_example_test.exs b/test/diffo_example_test.exs index d500bb4..61a86e7 100644 --- a/test/diffo_example_test.exs +++ b/test/diffo_example_test.exs @@ -7,4 +7,5 @@ defmodule DiffoExampleTest do use ExUnit.Case doctest DiffoExample.Access.Util doctest DiffoExample.Nbn.Util + doctest DiffoExample.Nbn.Speeds end From 04d7fefc960a233093a3b3e167ab0f666dc4205f Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Sat, 18 Apr 2026 16:59:42 +0930 Subject: [PATCH 08/13] removed String.Chars --- lib/access/resources/characteristic_values/cable_value.ex | 6 ------ lib/access/resources/characteristic_values/card_value.ex | 6 ------ lib/access/resources/characteristic_values/float_unit.ex | 6 ------ lib/access/resources/characteristic_values/integer_unit.ex | 6 ------ lib/access/resources/characteristic_values/path_value.ex | 6 ------ lib/access/resources/characteristic_values/shelf_value.ex | 6 ------ .../services/characteristic_values/aggregate_interface.ex | 6 ------ .../services/characteristic_values/bandwidth_profile.ex | 6 ------ lib/access/services/characteristic_values/circuit.ex | 6 ------ lib/access/services/characteristic_values/constraints.ex | 6 ------ lib/access/services/characteristic_values/dslam.ex | 6 ------ lib/access/services/characteristic_values/line.ex | 6 ------ lib/nbn/resources/characteristic_values/avc_value.ex | 6 ------ lib/nbn/resources/characteristic_values/cvc_value.ex | 6 ------ lib/nbn/resources/characteristic_values/nni_group_value.ex | 6 ------ lib/nbn/resources/characteristic_values/nni_value.ex | 6 ------ lib/nbn/resources/characteristic_values/ntd_value.ex | 6 ------ lib/nbn/resources/characteristic_values/pri_value.ex | 6 ------ lib/nbn/resources/characteristic_values/uni_value.ex | 6 ------ 19 files changed, 114 deletions(-) diff --git a/lib/access/resources/characteristic_values/cable_value.ex b/lib/access/resources/characteristic_values/cable_value.ex index 1c72707..e5a315a 100644 --- a/lib/access/resources/characteristic_values/cable_value.ex +++ b/lib/access/resources/characteristic_values/cable_value.ex @@ -30,10 +30,4 @@ defmodule DiffoExample.Access.CableValue do field :technology, :atom, description: "the cable technology" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/access/resources/characteristic_values/card_value.ex b/lib/access/resources/characteristic_values/card_value.ex index 7d59590..44441da 100644 --- a/lib/access/resources/characteristic_values/card_value.ex +++ b/lib/access/resources/characteristic_values/card_value.ex @@ -28,10 +28,4 @@ defmodule DiffoExample.Access.CardValue do field :technology, :atom, description: "the card technology" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/access/resources/characteristic_values/float_unit.ex b/lib/access/resources/characteristic_values/float_unit.ex index 7e75101..ad05cdf 100644 --- a/lib/access/resources/characteristic_values/float_unit.ex +++ b/lib/access/resources/characteristic_values/float_unit.ex @@ -24,10 +24,4 @@ defmodule DiffoExample.Access.FloatUnit do field :unit, :atom, description: "the unit" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/access/resources/characteristic_values/integer_unit.ex b/lib/access/resources/characteristic_values/integer_unit.ex index 4f9be8c..11d4c9b 100644 --- a/lib/access/resources/characteristic_values/integer_unit.ex +++ b/lib/access/resources/characteristic_values/integer_unit.ex @@ -24,10 +24,4 @@ defmodule DiffoExample.Access.IntegerUnit do field :unit, :atom, description: "the unit" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/access/resources/characteristic_values/path_value.ex b/lib/access/resources/characteristic_values/path_value.ex index fba2f11..7ea2e0e 100644 --- a/lib/access/resources/characteristic_values/path_value.ex +++ b/lib/access/resources/characteristic_values/path_value.ex @@ -33,10 +33,4 @@ defmodule DiffoExample.Access.PathValue do field :technology, :atom, description: "the path technology" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/access/resources/characteristic_values/shelf_value.ex b/lib/access/resources/characteristic_values/shelf_value.ex index e31da9f..80b1313 100644 --- a/lib/access/resources/characteristic_values/shelf_value.ex +++ b/lib/access/resources/characteristic_values/shelf_value.ex @@ -28,10 +28,4 @@ defmodule DiffoExample.Access.ShelfValue do field :technology, :atom, description: "the shelf technology" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/access/services/characteristic_values/aggregate_interface.ex b/lib/access/services/characteristic_values/aggregate_interface.ex index e733215..40d677c 100644 --- a/lib/access/services/characteristic_values/aggregate_interface.ex +++ b/lib/access/services/characteristic_values/aggregate_interface.ex @@ -52,10 +52,4 @@ defmodule DiffoExample.Access.AggregateInterface do default: 0, description: "the aggregate interface vpi" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/access/services/characteristic_values/bandwidth_profile.ex b/lib/access/services/characteristic_values/bandwidth_profile.ex index 5d49921..bd1c35b 100644 --- a/lib/access/services/characteristic_values/bandwidth_profile.ex +++ b/lib/access/services/characteristic_values/bandwidth_profile.ex @@ -33,10 +33,4 @@ defmodule DiffoExample.Access.BandwidthProfile do constraints: [one_of: [:kbps, :Mbps]], description: "the bandwidth profile units" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/access/services/characteristic_values/circuit.ex b/lib/access/services/characteristic_values/circuit.ex index 1488631..2aaddd4 100644 --- a/lib/access/services/characteristic_values/circuit.ex +++ b/lib/access/services/characteristic_values/circuit.ex @@ -44,10 +44,4 @@ defmodule DiffoExample.Access.Circuit do field :bandwidth_profile, BandwidthProfile, description: "the circuit bandwidth profile" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/access/services/characteristic_values/constraints.ex b/lib/access/services/characteristic_values/constraints.ex index 2b023f1..720e45f 100644 --- a/lib/access/services/characteristic_values/constraints.ex +++ b/lib/access/services/characteristic_values/constraints.ex @@ -29,10 +29,4 @@ defmodule DiffoExample.Access.Constraints do constraints: [instance_of: BandwidthProfile], description: "the circuit bandwidth profile" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/access/services/characteristic_values/dslam.ex b/lib/access/services/characteristic_values/dslam.ex index 5a2b92b..8042f43 100644 --- a/lib/access/services/characteristic_values/dslam.ex +++ b/lib/access/services/characteristic_values/dslam.ex @@ -36,10 +36,4 @@ defmodule DiffoExample.Access.Dslam do default: :eth, description: "the DSLAM technology" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/access/services/characteristic_values/line.ex b/lib/access/services/characteristic_values/line.ex index 67e6a80..dbc5624 100644 --- a/lib/access/services/characteristic_values/line.ex +++ b/lib/access/services/characteristic_values/line.ex @@ -35,10 +35,4 @@ defmodule DiffoExample.Access.Line do field :profile, :string, description: "the line port profile" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/nbn/resources/characteristic_values/avc_value.ex b/lib/nbn/resources/characteristic_values/avc_value.ex index 4060c1c..e298968 100644 --- a/lib/nbn/resources/characteristic_values/avc_value.ex +++ b/lib/nbn/resources/characteristic_values/avc_value.ex @@ -28,10 +28,4 @@ defmodule DiffoExample.Nbn.AvcValue do field :bandwidth_profile, BandwidthProfile, description: "the bandwidth profile of the AVC" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/nbn/resources/characteristic_values/cvc_value.ex b/lib/nbn/resources/characteristic_values/cvc_value.ex index 390d7c4..c67f791 100644 --- a/lib/nbn/resources/characteristic_values/cvc_value.ex +++ b/lib/nbn/resources/characteristic_values/cvc_value.ex @@ -28,10 +28,4 @@ defmodule DiffoExample.Nbn.CvcValue do constraints: [min: 0, max: 10000], description: "total CVC bandwidth in Mbps" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/nbn/resources/characteristic_values/nni_group_value.ex b/lib/nbn/resources/characteristic_values/nni_group_value.ex index bed82c3..9b1b72c 100644 --- a/lib/nbn/resources/characteristic_values/nni_group_value.ex +++ b/lib/nbn/resources/characteristic_values/nni_group_value.ex @@ -24,10 +24,4 @@ defmodule DiffoExample.Nbn.NniGroupValue do field :location, :string, description: "the Point of Interconnect (PoI) location" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/nbn/resources/characteristic_values/nni_value.ex b/lib/nbn/resources/characteristic_values/nni_value.ex index 6135bf2..a4d69b3 100644 --- a/lib/nbn/resources/characteristic_values/nni_value.ex +++ b/lib/nbn/resources/characteristic_values/nni_value.ex @@ -27,10 +27,4 @@ defmodule DiffoExample.Nbn.NniValue do field :technology, :atom, description: "the NNI technology (:Ethernet, :Fibre)" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/nbn/resources/characteristic_values/ntd_value.ex b/lib/nbn/resources/characteristic_values/ntd_value.ex index 2ff219e..03f0be7 100644 --- a/lib/nbn/resources/characteristic_values/ntd_value.ex +++ b/lib/nbn/resources/characteristic_values/ntd_value.ex @@ -30,10 +30,4 @@ defmodule DiffoExample.Nbn.NtdValue do description: "the access technology type", default: Technology.default() end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/nbn/resources/characteristic_values/pri_value.ex b/lib/nbn/resources/characteristic_values/pri_value.ex index 6c1bfb7..a678aeb 100644 --- a/lib/nbn/resources/characteristic_values/pri_value.ex +++ b/lib/nbn/resources/characteristic_values/pri_value.ex @@ -35,10 +35,4 @@ defmodule DiffoExample.Nbn.PriValue do field :speeds, Speeds, description: "the downstream and upstream speeds in Mbps" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end diff --git a/lib/nbn/resources/characteristic_values/uni_value.ex b/lib/nbn/resources/characteristic_values/uni_value.ex index 8bd5670..fcdd6a3 100644 --- a/lib/nbn/resources/characteristic_values/uni_value.ex +++ b/lib/nbn/resources/characteristic_values/uni_value.ex @@ -28,10 +28,4 @@ defmodule DiffoExample.Nbn.UniValue do field :technology, Technology, description: "the access technology type" end - - defimpl String.Chars do - def to_string(struct) do - inspect(struct) - end - end end From 7c7c846b66296d1c0697f336fe7c4dae3796b45a Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Fri, 24 Apr 2026 23:31:36 +0930 Subject: [PATCH 09/13] bump to v0.2.0 with NBN domain, livebook, and diffo 0.2.0 - version bumped to 0.2.0 to align with new NBN domain - updated to diffo 0.2.0 from hex - new NBN domain livebook (diffo_example.livemd) with Livebook badge in README - changelog updated with v0.2.0 entry --- CHANGELOG.md | 12 +- README.md | 5 +- diffo_example.livemd | 255 +++++++++++++++++++++++++++++++++++++++++++ mix.exs | 7 +- mix.lock | 8 +- 5 files changed, 278 insertions(+), 9 deletions(-) create mode 100644 diffo_example.livemd diff --git a/CHANGELOG.md b/CHANGELOG.md index bd008dd..9e2c60b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,4 +29,14 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v0.0.4](https://github.com/diffo-dev/diffo/compare/v0.0.3..v0.0.4) (2026-03-19) ### Fixes: -* fixed relationship enrichment inconsistent across neo4j versions \ No newline at end of file +* fixed relationship enrichment inconsistent across neo4j versions + +## [v0.2.0](https://github.com/diffo-dev/diffo/compare/v0.0.4..v0.2.0) (2026-04-24) + +### Maintenance: +* updated to diffo 0.2.0 + +### Features: +* new NBN domain modelling NBN Ethernet access and constituent resources (UNI, AVC, NTD, CVC, NNI Group, NNI) +* NBN Technology and Speeds as Ash Enum types +* speeds derived from NTD technology and AVC bandwidth_profile via mine action \ No newline at end of file diff --git a/README.md b/README.md index 14440e8..adddd0e 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ SPDX-License-Identifier: MIT [![Module Version](https://img.shields.io/hexpm/v/diffo)](https://hex.pm/packages/diffo_example) [![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen)](https://hexdocs.pm/diffo_example/) +[![Run in Livebook](https://livebook.dev/badge/v1/blue.svg)](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fdiffo-dev%2Fdiffo_example%2Fblob%2Fmain%2Fdiffo_example.livemd) [![License](https://img.shields.io/hexpm/l/diffo)](https://github.com/diffo-dev/diffo_example/blob/master/LICENSES/MIT.md) [![REUSE status](https://api.reuse.software/badge/github.com/diffo-dev/diffo_example)](https://api.reuse.software/info/github.com/diffo-dev/diffo_example) @@ -33,7 +34,9 @@ You need [Neo4j](https://github.com/neo4j/neo4j) available. We recommend the Neo ## Tutorial -Diffo has a livebook and you should use this as an introduction. +Click the **Run in Livebook** badge above to open the interactive tutorial, or find it at [diffo_example.livemd](diffo_example.livemd). + +The diffo_example livebook walks through provisioning a complete NBN Ethernet access circuit — NTD, UNI, AVC, CVC, NNI Group, and NNI — showing how the `mine` actions propagate technology, speeds, CVLAN, and port assignments up the resource hierarchy. ## Contributions diff --git a/diffo_example.livemd b/diffo_example.livemd new file mode 100644 index 0000000..3dc40c2 --- /dev/null +++ b/diffo_example.livemd @@ -0,0 +1,255 @@ + + +# Diffo Example - NBN Domain + +```elixir +Mix.install( + [ + {:diffo_example, "~> 0.2.0"} + ], + consolidate_protocols: false +) +``` + +## Overview + +[Diffo](https://github.com/diffo-dev/diffo) is a Telecommunications Management Forum (TMF) Service and Resource Manager, built for autonomous networks. It is implemented using the [Ash Framework](https://www.ash-hq.org) and stores data in Neo4j via [AshNeo4j](https://github.com/diffo-dev/ash_neo4j). + +If you are new to Diffo, start with the [Diffo livebook](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fdiffo-dev%2Fdiffo%2Fblob%2Fdev%2Fdiffo.livemd) which introduces the core Provider concepts — Specification, Instance, Feature, Characteristic, Party, Place, and Relationship. + +Diffo includes a Provider Instance extension that lets you declare specialised TMF Services and Resources using a Spark DSL with very little Elixir code. The [Provider Instance Extension livebook](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fdiffo-dev%2Fdiffo%2Fblob%2Fdev%2Fdocumentation%2Fhow_to%2Fuse_diffo_provider_instance_extension.livemd) covers this in detail. + +The NBN domain in this example was built entirely with that DSL — a declarative model of a realistic NBN Ethernet access hierarchy with minimal custom Elixir, derived from a short domain description. It demonstrates how much can be expressed through the Provider extension alone. + +The NBN domain models a fictional NBN Ethernet access circuit and its constituent resources: + +* **NbnEthernet** — the parent circuit resource (identified by a PRI) +* **UNI** — User Network Interface at the customer premises +* **AVC** — Access Virtual Circuit (dedicated, carries traffic between UNI and CVC) +* **NTD** — Network Termination Device (installed at customer premises, assigns ports to UNI) +* **CVC** — Connectivity Virtual Circuit (aggregates AVCs, terminates at NNI Group) +* **NNI Group** — group of NNIs at the point of interconnect +* **NNI** — Network-to-Network Interface + +## Installing Neo4j and Configuring Bolty + +Update the configuration below to match your Neo4j installation and evaluate. + +```elixir +config = [ + uri: "bolt://localhost:7687", + auth: [username: "neo4j", password: "password"], + user_agent: "diffoExampleLivebook/1", + pool_size: 15, + max_overflow: 3, + prefix: :default, + name: Bolt, + log: false, + log_hex: false +] +``` + +```elixir +AshNeo4j.BoltyHelper.start(config) +``` + +```elixir +AshNeo4j.BoltyHelper.is_connected() +``` + +It is helpful to have a Neo4j browser open locally, typically at http://localhost:7474/browser/ + +**OPTIONAL** Clear the database before starting: + +```elixir +AshNeo4j.Neo4jHelper.delete_all() +``` + +## Setup Aliases + +```elixir +require Ash.Query +alias DiffoExample.Nbn +alias DiffoExample.Nbn.NbnEthernet +alias DiffoExample.Nbn.Uni +alias DiffoExample.Nbn.Avc +alias DiffoExample.Nbn.Ntd +alias DiffoExample.Nbn.Cvc +alias DiffoExample.Nbn.NniGroup +alias DiffoExample.Nbn.Nni +alias DiffoExample.Nbn.Technology +alias DiffoExample.Nbn.Speeds +import Jason, only: [encode: 2] +``` + +## About NBN + +NBN (National Broadband Network) is Australia's wholesale fixed-line access network, operated by NBN Co. It provides standardised access products to Retail Service Providers (RSPs), who in turn deliver internet and other services to end customers. + +An RSP typically combines: + +* An **NBN Ethernet** access circuit (UNI + AVC) at the customer premises — the access and aggregation layer modelled in this domain +* A **home gateway** device installed at the UNI, which provides the customer's LAN, Wi-Fi, and sometimes voice +* Transport, aggregation, and edge infrastructure connecting the NNI to the RSP's network and on to the internet + +NBN Co connects the customer premises to the RSP's network via a Point of Interconnect (POI). The NNI sits at the POI, grouped into NNI Groups. AVCs carrying customer traffic are aggregated onto a CVC, which terminates at the NNI Group. The RSP purchases CVC capacity to carry the aggregate traffic of its customers at that POI. + +NBN is delivered over several access technologies — FTTP, FTTN, FTTB, FTTC, HFC, Fixed Wireless, and Satellite — which determine which bandwidth profiles and speeds are available to a given premises. + +## Technology and Speeds + +The NBN domain defines Technology as an Ash Enum covering all NBN access types: + +```elixir +Technology.values() +``` + +Speeds are derived from a bandwidth_profile and technology combination. For example: + +```elixir +Speeds.speeds(:home_fast, :FTTP) +``` + +```elixir +Speeds.speeds(:home_hyperfast, :HFC) +``` + +```elixir +Speeds.speeds(:wireless_superfast, :FixedWireless) +``` + +```elixir +# returns :error for invalid combinations +Speeds.speeds(:home_fast, :FixedWireless) +``` + +## Building the Network Hinterland + +Before we can provision an NBN Ethernet access we need the shared network resources: NNI, NNI Group, and CVC. + +Build an NNI — the physical interconnect between the RSP and NBN Co: + +```elixir +nni = Nbn.build_nni!(%{}) +nni |> Jason.encode!(pretty: true) |> IO.puts +``` + +Build an NNI Group — a logical grouping of NNIs at a point of interconnect: + +```elixir +nni_group = Nbn.build_nni_group!(%{}) +nni_group |> Jason.encode!(pretty: true) |> IO.puts +``` + +Define the NNI Group with an SVLAN assignment and relate the NNI: + +```elixir +nni_group = Nbn.define_nni_group!(%{ + characteristic_value_updates: [nni_group: [svlan: 100]] +}) +nni_group = Nbn.relate_nni_group!(nni_group, %{ + relationships: [%{alias: :nni, target_id: nni.id, type: :isAssigned}] +}) +nni_group |> Jason.encode!(pretty: true) |> IO.puts +``` + +Build a CVC — the aggregation virtual circuit that terminates at the NNI Group: + +```elixir +cvc = Nbn.build_cvc!(%{}) +cvc = Nbn.relate_cvc!(cvc, %{ + relationships: [%{alias: :nni_group, target_id: nni_group.id, type: :isAssigned}] +}) +cvc |> Jason.encode!(pretty: true) |> IO.puts +``` + +## Provisioning an NBN Ethernet Access + +With the hinterland in place we can provision a customer-facing NBN Ethernet access. + +Build an NTD — the device installed at the customer premises: + +```elixir +ntd = Nbn.build_ntd!(%{}) +ntd = Nbn.define_ntd!(ntd, %{ + characteristic_value_updates: [ntd: [technology: :FTTP, ports: [1, 2, 3, 4]]] +}) +ntd |> Jason.encode!(pretty: true) |> IO.puts +``` + +Build a UNI — the interface at the customer premises — and assign a port from the NTD: + +```elixir +uni = Nbn.build_uni!(%{}) +alias Diffo.Provider.Assignment +ntd = Nbn.assign_port!(ntd, %{ + assignment: %Assignment{assignee_id: uni.id, value: 1} +}) +ntd |> Jason.encode!(pretty: true) |> IO.puts +``` + +Relate the UNI back to the NTD so it can mine technology and port from it: + +```elixir +uni = Nbn.relate_uni!(uni, %{ + relationships: [%{alias: :ntd, target_id: ntd.id, type: :isAssigned}] +}) +uni = Nbn.mine_uni!(uni, %{}) +uni |> Jason.encode!(pretty: true) |> IO.puts +``` + +Build an AVC and assign it a CVLAN from the CVC: + +```elixir +avc = Nbn.build_avc!(%{}) +avc = Nbn.define_avc!(avc, %{ + characteristic_value_updates: [avc: [bandwidth_profile: :home_ultrafast]] +}) +cvc = Nbn.assign_cvlan!(cvc, %{ + assignment: %Assignment{assignee_id: avc.id, value: 200} +}) +avc = Nbn.mine_avc!(avc, %{}) +avc |> Jason.encode!(pretty: true) |> IO.puts +``` + +Now build the top-level NBN Ethernet access and relate it to both the UNI and AVC: + +```elixir +pri = Nbn.build_nbn_ethernet!(%{}) +pri = Nbn.relate_nbn_ethernet!(pri, %{ + relationships: [ + %{alias: :uni, target_id: uni.id, type: :isAssigned}, + %{alias: :avc, target_id: avc.id, type: :isAssigned} + ] +}) +pri = Nbn.mine_nbn_ethernet!(pri, %{}) +pri |> Jason.encode!(pretty: true) |> IO.puts +``` + +The `mine` action on NbnEthernet extracts technology from the UNI and bandwidth_profile from the AVC and derives the speeds automatically. + +## Exploring the Graph + +You can query all nodes and relationships in Neo4j browser with: + +```cypher +MATCH (n1)-[r]->(n2) RETURN r, n1, n2 LIMIT 50 +``` + +Or from Elixir: + +```elixir +AshNeo4j.Cypher.run("MATCH (n1)-[r]->(n2) RETURN r, n1, n2 LIMIT 50") +``` + +## What Next? + +You've provisioned a complete NBN Ethernet access — NTD, UNI, AVC, CVC, NNI Group, and NNI — and seen how the `mine` actions propagate technology, speeds, CVLAN and port assignments up the resource hierarchy automatically. + +The Access domain in `diffo_example` shows a similar pattern for DSL access services. Explore `lib/access/` for copper-network equivalents (Cable, Card, Path, Shelf). + +If you find Diffo useful please visit and star on [GitHub](https://github.com/diffo-dev/diffo/). diff --git a/mix.exs b/mix.exs index 496ce1d..7f33924 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule DiffoExample.MixProject do @moduledoc false use Mix.Project - @version "0.0.4" + @version "0.2.0" @name "DiffoExample" @description "Examples for Diffo TMF Service and Resource Manager" @github_url "https://github.com/diffo-dev/diffo-example" @@ -45,6 +45,7 @@ defmodule DiffoExample.MixProject do nil -> default_version "local" -> [path: "../diffo"] "main" -> [git: "https://github.com/diffo-dev/diffo.git"] + "0.2.0" -> [git: "https://github.com/diffo-dev/diffo.git", tag: "v0.2.0"] version -> "~> #{version}" end end @@ -58,6 +59,7 @@ defmodule DiffoExample.MixProject do logo: "logos/diffo.jpg", extras: [ "README.md": [title: "Guide"], + "diffo_example.livemd": [title: "Livebook Tutorial"], "LICENSES/MIT.md": [title: "License"] ] ] @@ -78,8 +80,7 @@ defmodule DiffoExample.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - # {:diffo, diffo_version("~> 0.1.6")}, - {:diffo, github: "diffo-dev/diffo", branch: "dev"}, + {:diffo, diffo_version("~> 0.2.0")}, {:igniter, "~> 0.6", only: [:dev, :test]}, {:ex_doc, "~> 0.37", only: [:dev, :test], runtime: false} ] diff --git a/mix.lock b/mix.lock index 3a8d2e0..04acf2a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,14 +1,14 @@ %{ "ash": {:hex, :ash, "3.24.3", "f7280a43c5e64f769a450f3dd59ace6dcd73edcdd0de7599815b1b31f59292fb", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.6.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1022f8c549632137cbc8956f07bb4981405297f5abe7a752b4dffac175c3381"}, "ash_jason": {:hex, :ash_jason, "3.1.0", "84a88dfe5e25a20d55cf2d2664885cd086fa45871e8777aedc3ad96a282e2a6f", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:spark, ">= 2.1.21 and < 3.0.0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "71e6bbc421fb2cf7079f8804814145cca458116c839fc798f9606b806e07eb2b"}, - "ash_neo4j": {:git, "https://github.com/diffo-dev/ash_neo4j.git", "044d9d123af30719a9f1f377e2c24b5cc8e21ea8", [branch: "dev"]}, + "ash_neo4j": {:hex, :ash_neo4j, "0.3.1", "52b81e870d020815ffb2699f3fa207e10e909418e80c8aec4c64ed668418299a", [:mix], [{:ash, ">= 3.24.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:bolty, ">= 0.0.10", [hex: :bolty, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "5da556d93e03fda97e1bb626941114b7011a64173b1c10deb12cf66523e82001"}, "ash_outstanding": {:hex, :ash_outstanding, "0.2.4", "c72b91f1b8e4859fb033eddf66d0ba36cfd8af0c2a9748c7ef9e6ccfdb5d093d", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:outstanding, "~> 0.2.4", [hex: :outstanding, repo: "hexpm", optional: false]}], "hexpm", "64ba8f582ce69c9050352c75f0895db186c7a56f35039dab34c8e1ab7516f9ce"}, "ash_state_machine": {:hex, :ash_state_machine, "0.2.13", "e1c368ebf01ef73477739ee76d53e513d073b141ec11e7bf7f91d8f2d8fc9569", [:mix], [{:ash, ">= 3.4.66 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}], "hexpm", "aa21c92a8950850df69b5205bf41efc1e502f5ab839425ba08561f0421c9f226"}, - "bolty": {:hex, :bolty, "0.0.9", "c8026ce9804347f71e23b3a0cbc01b918ef94b61e159b5ba7fb48527878033ad", [:mix], [{:db_connection, "~> 2.7.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "fc20c42550c0fce370276b4ef119e92792761b2fea1aef9cccf8de946bc39d35"}, + "bolty": {:hex, :bolty, "0.0.10", "ec88948d30cfc213cdb1168f86d96cdcadd80f16e4f29701966e69dfbac43ded", [:mix], [{:db_connection, "~> 2.7.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "2ce63d6c23301d1c9a61fd29ef06ebb7d2e775d4fd4144e86c2717aa43f409c9"}, "crux": {:hex, :crux, "0.1.2", "4441c9e3a34f1e340954ce96b9ad5a2de13ceb4f97b3f910211227bb92e2ca90", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "563ea3748ebfba9cc078e6d198a1d6a06015a8fae503f0b721363139f0ddb350"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, - "diffo": {:git, "https://github.com/diffo-dev/diffo.git", "9640dd460ce149e4cc4e3dd1308746ce7c465597", [branch: "dev"]}, + "diffo": {:hex, :diffo, "0.2.0", "ac07bb5ea92d765601fba3e61e8a5dac5c3c7f18b3a55bcf3019a574fda03d65", [:mix], [{:ash, ">= 3.24.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_jason, "~> 3.0", [hex: :ash_jason, repo: "hexpm", optional: false]}, {:ash_neo4j, "~> 0.3.1", [hex: :ash_neo4j, repo: "hexpm", optional: false]}, {:ash_outstanding, "~> 0.2.3", [hex: :ash_outstanding, repo: "hexpm", optional: false]}, {:ash_state_machine, "~> 0.2.12", [hex: :ash_state_machine, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm", "2a140d9e427e30b06b29a04eeafec8b98d7acfeaffdbfa06cf6c152998302503"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, @@ -35,7 +35,7 @@ "rewrite": {:hex, :rewrite, "1.3.0", "67448ba7975690b35ba7e7f35717efcce317dbd5963cb0577aa7325c1923121a", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "d111ac7ff3a58a802ef4f193bbd1831e00a9c57b33276e5068e8390a212714a5"}, "sourceror": {:hex, :sourceror, "1.12.0", "da354c5f35aad3cc1132f5d5b0d8437d865e2661c263260480bab51b5eedb437", [:mix], [], "hexpm", "755703683bd014ebcd5de9acc24b68fb874a660a568d1d63f8f98cd8a6ef9cd0"}, "spark": {:hex, :spark, "2.6.1", "b0100216d3883c6a281cb2434af45afbd808695aadb034923cbaf7d8a2ba46ab", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "77bbefa5263bb6b70e1195bc0fc662ddb8ef5937a356a77ae072e56983ad13f0"}, - "spitfire": {:hex, :spitfire, "0.3.10", "19aea9914132456515e8f7d592f63ab9f3130876b0252e834d2390bdd8becb24", [:mix], [], "hexpm", "6a6a5f77eb4165249c76199cd2d01fb595bac9207aed3de551918ac1c2bc9267"}, + "spitfire": {:hex, :spitfire, "0.3.11", "79dfcb033762470de472c1c26ea2b4e3aca74700c685dbffd9a13466272c323d", [:mix], [], "hexpm", "eb6e2dadf63214e8bfe65ca9788cef2b03b01027365d78d3c0e3d9ebd3d5b7b4"}, "splode": {:hex, :splode, "0.3.1", "9843c54f84f71b7833fec3f0be06c3cfb5be6b35960ee195ea4fad84b1c25030", [:mix], [], "hexpm", "8f2309b6ec2ecbb01435656429ed1d9ed04ba28797a3280c3b0d1217018ecfbd"}, "stream_data": {:hex, :stream_data, "1.3.0", "bde37905530aff386dea1ddd86ecbf00e6642dc074ceffc10b7d4e41dfd6aac9", [:mix], [], "hexpm", "3cc552e286e817dca43c98044c706eec9318083a1480c52ae2688b08e2936e3c"}, "telemetry": {:hex, :telemetry, "1.4.1", "ab6de178e2b29b58e8256b92b382ea3f590a47152ca3651ea857a6cae05ac423", [:rebar3], [], "hexpm", "2172e05a27531d3d31dd9782841065c50dd5c3c7699d95266b2edd54c2dafa1c"}, From eb76a935bbc4a0d6ddc7558db0ace4433804bd3d Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Sat, 25 Apr 2026 02:10:48 +0930 Subject: [PATCH 10/13] json api --- .claude/settings.json | 10 ++++ CHANGELOG.md | 4 +- config/config.exs | 1 + diffo_example.livemd | 74 +++++++++++++++++++----------- lib/diffo_example/application.ex | 7 ++- lib/nbn/api_router.ex | 10 ++++ lib/nbn/catalog.ex | 25 ++++++++++ lib/nbn/initializer.ex | 26 +++++++++++ lib/nbn/nbn.ex | 76 ++++++++++++++++++++++++++++++- lib/nbn/resources/avc.ex | 11 +++-- lib/nbn/resources/cvc.ex | 7 ++- lib/nbn/resources/nbn_ethernet.ex | 10 +++- lib/nbn/resources/nni.ex | 7 ++- lib/nbn/resources/nni_group.ex | 7 ++- lib/nbn/resources/ntd.ex | 7 ++- lib/nbn/resources/uni.ex | 11 +++-- lib/nbn/router.ex | 35 ++++++++++++++ mix.exs | 3 ++ mix.lock | 16 +++++++ 19 files changed, 303 insertions(+), 44 deletions(-) create mode 100644 .claude/settings.json create mode 100644 lib/nbn/api_router.ex create mode 100644 lib/nbn/catalog.ex create mode 100644 lib/nbn/initializer.ex create mode 100644 lib/nbn/router.ex diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..e421007 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(mix deps.get)", + "Bash(git -C /Users/beanlanda/git/diffo_example status)", + "Read(//Users/beanlanda/git/**)", + "Read(//Users/beanlanda/.mix/**)" + ] + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e2c60b..b033778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,4 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline * updated to diffo 0.2.0 ### Features: -* new NBN domain modelling NBN Ethernet access and constituent resources (UNI, AVC, NTD, CVC, NNI Group, NNI) -* NBN Technology and Speeds as Ash Enum types -* speeds derived from NTD technology and AVC bandwidth_profile via mine action \ No newline at end of file +* new NBN domain modelling NBN Ethernet access and constituent resources (UNI, AVC, NTD, CVC, NNI Group, NNI), JSON API and livebook \ No newline at end of file diff --git a/config/config.exs b/config/config.exs index ff4d1b4..9920208 100644 --- a/config/config.exs +++ b/config/config.exs @@ -16,6 +16,7 @@ config :spark, :characteristics, :neo4j, :jason, + :json_api, :outstanding, :actions, :state_machine, diff --git a/diffo_example.livemd b/diffo_example.livemd index 3dc40c2..830be14 100644 --- a/diffo_example.livemd +++ b/diffo_example.livemd @@ -9,7 +9,21 @@ SPDX-License-Identifier: MIT ```elixir Mix.install( [ - {:diffo_example, "~> 0.2.0"} + {:diffo_example, "~> 0.2.0"}, + {:req, "~> 0.5"} + ], + config: [ + bolty: [{Bolt, [ + uri: "bolt://localhost:7687", + auth: [username: "neo4j", password: "password"], + user_agent: "diffoExampleLivebook/1", + pool_size: 15, + max_overflow: 3, + prefix: :default, + name: Bolt, + log: false, + log_hex: false + ]}] ], consolidate_protocols: false ) @@ -37,25 +51,9 @@ The NBN domain models a fictional NBN Ethernet access circuit and its constituen ## Installing Neo4j and Configuring Bolty -Update the configuration below to match your Neo4j installation and evaluate. +Bolty is configured in the `Mix.install` block above — update the Neo4j credentials there if needed before evaluating. -```elixir -config = [ - uri: "bolt://localhost:7687", - auth: [username: "neo4j", password: "password"], - user_agent: "diffoExampleLivebook/1", - pool_size: 15, - max_overflow: 3, - prefix: :default, - name: Bolt, - log: false, - log_hex: false -] -``` - -```elixir -AshNeo4j.BoltyHelper.start(config) -``` +You need [Neo4j](https://neo4j.com/deployment-center/) installed and running. Verify the connection: ```elixir AshNeo4j.BoltyHelper.is_connected() @@ -148,11 +146,11 @@ nni_group |> Jason.encode!(pretty: true) |> IO.puts Define the NNI Group with an SVLAN assignment and relate the NNI: ```elixir -nni_group = Nbn.define_nni_group!(%{ +nni_group = Nbn.define_nni_group!(nni_group, %{ characteristic_value_updates: [nni_group: [svlan: 100]] }) nni_group = Nbn.relate_nni_group!(nni_group, %{ - relationships: [%{alias: :nni, target_id: nni.id, type: :isAssigned}] + relationships: [%Diffo.Provider.Instance.Relationship{id: nni.id, alias: :nni, type: :isAssigned}] }) nni_group |> Jason.encode!(pretty: true) |> IO.puts ``` @@ -162,7 +160,7 @@ Build a CVC — the aggregation virtual circuit that terminates at the NNI Group ```elixir cvc = Nbn.build_cvc!(%{}) cvc = Nbn.relate_cvc!(cvc, %{ - relationships: [%{alias: :nni_group, target_id: nni_group.id, type: :isAssigned}] + relationships: [%Diffo.Provider.Instance.Relationship{id: nni_group.id, alias: :nni_group, type: :isAssigned}] }) cvc |> Jason.encode!(pretty: true) |> IO.puts ``` @@ -187,7 +185,7 @@ Build a UNI — the interface at the customer premises — and assign a port fro uni = Nbn.build_uni!(%{}) alias Diffo.Provider.Assignment ntd = Nbn.assign_port!(ntd, %{ - assignment: %Assignment{assignee_id: uni.id, value: 1} + assignment: %Assignment{assignee_id: uni.id, operation: :auto_assign} }) ntd |> Jason.encode!(pretty: true) |> IO.puts ``` @@ -196,7 +194,7 @@ Relate the UNI back to the NTD so it can mine technology and port from it: ```elixir uni = Nbn.relate_uni!(uni, %{ - relationships: [%{alias: :ntd, target_id: ntd.id, type: :isAssigned}] + relationships: [%Diffo.Provider.Instance.Relationship{id: ntd.id, alias: :ntd, type: :isAssigned}] }) uni = Nbn.mine_uni!(uni, %{}) uni |> Jason.encode!(pretty: true) |> IO.puts @@ -210,7 +208,7 @@ avc = Nbn.define_avc!(avc, %{ characteristic_value_updates: [avc: [bandwidth_profile: :home_ultrafast]] }) cvc = Nbn.assign_cvlan!(cvc, %{ - assignment: %Assignment{assignee_id: avc.id, value: 200} + assignment: %Assignment{assignee_id: avc.id, operation: :auto_assign} }) avc = Nbn.mine_avc!(avc, %{}) avc |> Jason.encode!(pretty: true) |> IO.puts @@ -222,8 +220,8 @@ Now build the top-level NBN Ethernet access and relate it to both the UNI and AV pri = Nbn.build_nbn_ethernet!(%{}) pri = Nbn.relate_nbn_ethernet!(pri, %{ relationships: [ - %{alias: :uni, target_id: uni.id, type: :isAssigned}, - %{alias: :avc, target_id: avc.id, type: :isAssigned} + %Diffo.Provider.Instance.Relationship{id: uni.id, alias: :uni, type: :isAssigned}, + %Diffo.Provider.Instance.Relationship{id: avc.id, alias: :avc, type: :isAssigned} ] }) pri = Nbn.mine_nbn_ethernet!(pri, %{}) @@ -246,6 +244,28 @@ Or from Elixir: AshNeo4j.Cypher.run("MATCH (n1)-[r]->(n2) RETURN r, n1, n2 LIMIT 50") ``` +## JSON API + +The NBN domain exposes a JSON API via `Plug.Cowboy` on port 4000. Start the server in your application before evaluating these cells. + +First check the catalog — all NBN specifications are initialised on startup: + +```elixir +Req.get!("http://localhost:4000/catalog").body |> Jason.encode!(pretty: true) |> IO.puts() +``` + +Now retrieve all NBN Ethernet instances: + +```elixir +Req.get!("http://localhost:4000/nbnEthernet").body |> Jason.encode!(pretty: true) |> IO.puts() +``` + +Or fetch the one we provisioned above by id: + +```elixir +Req.get!("http://localhost:4000/nbnEthernet/#{pri.id}").body |> Jason.encode!(pretty: true) |> IO.puts() +``` + ## What Next? You've provisioned a complete NBN Ethernet access — NTD, UNI, AVC, CVC, NNI Group, and NNI — and seen how the `mine` actions propagate technology, speeds, CVLAN and port assignments up the resource hierarchy automatically. diff --git a/lib/diffo_example/application.ex b/lib/diffo_example/application.ex index a9b8835..8bca8f1 100644 --- a/lib/diffo_example/application.ex +++ b/lib/diffo_example/application.ex @@ -9,6 +9,11 @@ defmodule DiffoExample.Application do @impl true def start(_type, _args) do - Supervisor.start_link([], strategy: :one_for_one) + children = [ + {Plug.Cowboy, scheme: :http, plug: DiffoExample.Nbn.Router, options: [port: 4000]}, + {Task, &DiffoExample.Nbn.Initializer.init/0} + ] + + Supervisor.start_link(children, strategy: :one_for_one, name: DiffoExample.Supervisor) end end diff --git a/lib/nbn/api_router.ex b/lib/nbn/api_router.ex new file mode 100644 index 0000000..ffa0828 --- /dev/null +++ b/lib/nbn/api_router.ex @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.ApiRouter do + @moduledoc false + use AshJsonApi.Router, + domains: [DiffoExample.Nbn], + open_api: "/open_api" +end diff --git a/lib/nbn/catalog.ex b/lib/nbn/catalog.ex new file mode 100644 index 0000000..3a3ab3f --- /dev/null +++ b/lib/nbn/catalog.ex @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.Catalog do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + Catalog - the NBN resource and service catalog. + """ + + def list do + Diffo.Provider.list_specifications!() + |> Enum.map(fn spec -> + Jason.OrderedObject.new( + id: spec.id, + href: spec.href, + name: spec.name, + version: spec.version, + description: spec.description, + category: spec.category + ) + end) + end +end diff --git a/lib/nbn/initializer.ex b/lib/nbn/initializer.ex new file mode 100644 index 0000000..d261b87 --- /dev/null +++ b/lib/nbn/initializer.ex @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.Initializer do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + Initializes the NBN domain's specifications in the catalog on application startup, + so the catalog is populated before any instances are built. + """ + + alias Diffo.Provider.Instance.Specification + + def init do + DiffoExample.Nbn + |> Ash.Domain.Info.resources() + |> Enum.each(fn module -> + try do + Specification.upsert_specification(module) + rescue + _ -> :ok + end + end) + end +end diff --git a/lib/nbn/nbn.ex b/lib/nbn/nbn.ex index 3ae584e..b739947 100644 --- a/lib/nbn/nbn.ex +++ b/lib/nbn/nbn.ex @@ -13,7 +13,8 @@ defmodule DiffoExample.Nbn do CVC (aggregates AVCs, terminates at NNI Group), NNI Group, and NNI. """ use Ash.Domain, - otp_app: :diffo + otp_app: :diffo, + extensions: [AshJsonApi.Domain] alias DiffoExample.Nbn.NbnEthernet alias DiffoExample.Nbn.Uni @@ -27,6 +28,78 @@ defmodule DiffoExample.Nbn do description "An example showing how TMF Resources for a fictional NBN domain can be extended from the Provider domain" end + json_api do + routes do + base_route "/nbnEthernet", NbnEthernet do + index :read + get :read + post :build + patch :define + patch :relate, route: "/:id/relate" + patch :mine, route: "/:id/mine" + delete :destroy + end + + base_route "/uni", Uni do + index :read + get :read + post :build + patch :define + patch :relate, route: "/:id/relate" + patch :mine, route: "/:id/mine" + delete :destroy + end + + base_route "/avc", Avc do + index :read + get :read + post :build + patch :define + patch :relate, route: "/:id/relate" + patch :mine, route: "/:id/mine" + delete :destroy + end + + base_route "/ntd", Ntd do + index :read + get :read + post :build + patch :define + patch :relate, route: "/:id/relate" + delete :destroy + end + + base_route "/cvc", Cvc do + index :read + get :read + post :build + patch :define + patch :relate, route: "/:id/relate" + patch :mine, route: "/:id/mine" + delete :destroy + end + + base_route "/nniGroup", NniGroup do + index :read + get :read + post :build + patch :define + patch :relate, route: "/:id/relate" + delete :destroy + end + + base_route "/nni", Nni do + index :read + get :read + post :build + patch :define + patch :relate, route: "/:id/relate" + delete :destroy + end + + end + end + resources do resource NbnEthernet do define :get_nbn_ethernet_by_id, action: :read, get_by: :id @@ -83,5 +156,6 @@ defmodule DiffoExample.Nbn do define :define_nni, action: :define define :relate_nni, action: :relate end + end end diff --git a/lib/nbn/resources/avc.ex b/lib/nbn/resources/avc.ex index 467d699..9924932 100644 --- a/lib/nbn/resources/avc.ex +++ b/lib/nbn/resources/avc.ex @@ -21,7 +21,12 @@ defmodule DiffoExample.Nbn.Avc do use Ash.Resource, fragments: [BaseInstance], - domain: Nbn + domain: Nbn, + extensions: [AshJsonApi.Resource] + + json_api do + type "avc" + end resource do description "An Ash Resource representing an Access Virtual Circuit (AVC)" @@ -110,9 +115,9 @@ defmodule DiffoExample.Nbn.Avc do # mines related resource to characteristics def mine_related(changeset, _context) when is_struct(changeset, Ash.Changeset) do - reverse_relationships = Ash.Changeset.get_attribute(changeset, :reverse_relationships) + avc = Ash.load!(changeset.data, [reverse_relationships: [:characteristics]]) - cvlan = {:cvlan, Diffo.Unwrap.unwrap(hd(hd(reverse_relationships).characteristics).value)} + cvlan = {:cvlan, Diffo.Unwrap.unwrap(hd(hd(avc.reverse_relationships).characteristics).value)} Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, avc: [cvlan]) end diff --git a/lib/nbn/resources/cvc.ex b/lib/nbn/resources/cvc.ex index 8e75dcf..81d67a2 100644 --- a/lib/nbn/resources/cvc.ex +++ b/lib/nbn/resources/cvc.ex @@ -23,7 +23,12 @@ defmodule DiffoExample.Nbn.Cvc do use Ash.Resource, fragments: [BaseInstance], - domain: Nbn + domain: Nbn, + extensions: [AshJsonApi.Resource] + + json_api do + type "cvc" + end resource do description "An Ash Resource representing a Connectivity Virtual Circuit (CVC)" diff --git a/lib/nbn/resources/nbn_ethernet.ex b/lib/nbn/resources/nbn_ethernet.ex index d4594a5..26e37ca 100644 --- a/lib/nbn/resources/nbn_ethernet.ex +++ b/lib/nbn/resources/nbn_ethernet.ex @@ -22,7 +22,12 @@ defmodule DiffoExample.Nbn.NbnEthernet do use Ash.Resource, fragments: [BaseInstance], - domain: Nbn + domain: Nbn, + extensions: [AshJsonApi.Resource] + + json_api do + type "nbnEthernet" + end resource do description "An Ash Resource representing an NBN Ethernet access" @@ -114,7 +119,8 @@ defmodule DiffoExample.Nbn.NbnEthernet do # mines related resource to characteristics def mine_related(changeset, _context) when is_struct(changeset, Ash.Changeset) do - forward_relationships = Ash.Changeset.get_attribute(changeset, :forward_relationships) + pri = Ash.load!(changeset.data, [:forward_relationships]) + forward_relationships = pri.forward_relationships pri_updates = Enum.reduce(forward_relationships, [], fn forward_relationship, acc -> diff --git a/lib/nbn/resources/nni.ex b/lib/nbn/resources/nni.ex index 92785a5..e9b84f6 100644 --- a/lib/nbn/resources/nni.ex +++ b/lib/nbn/resources/nni.ex @@ -22,7 +22,12 @@ defmodule DiffoExample.Nbn.Nni do use Ash.Resource, fragments: [BaseInstance], - domain: Nbn + domain: Nbn, + extensions: [AshJsonApi.Resource] + + json_api do + type "nni" + end resource do description "An Ash Resource representing a Network-to-Network Interface (NNI)" diff --git a/lib/nbn/resources/nni_group.ex b/lib/nbn/resources/nni_group.ex index 9629d5b..9e89a33 100644 --- a/lib/nbn/resources/nni_group.ex +++ b/lib/nbn/resources/nni_group.ex @@ -24,7 +24,12 @@ defmodule DiffoExample.Nbn.NniGroup do use Ash.Resource, fragments: [BaseInstance], - domain: Nbn + domain: Nbn, + extensions: [AshJsonApi.Resource] + + json_api do + type "nniGroup" + end resource do description "An Ash Resource representing an NNI Group" diff --git a/lib/nbn/resources/ntd.ex b/lib/nbn/resources/ntd.ex index b9fa3ea..c7a66e5 100644 --- a/lib/nbn/resources/ntd.ex +++ b/lib/nbn/resources/ntd.ex @@ -23,7 +23,12 @@ defmodule DiffoExample.Nbn.Ntd do use Ash.Resource, fragments: [BaseInstance], - domain: Nbn + domain: Nbn, + extensions: [AshJsonApi.Resource] + + json_api do + type "ntd" + end resource do description "An Ash Resource representing a Network Termination Device (NTD)" diff --git a/lib/nbn/resources/uni.ex b/lib/nbn/resources/uni.ex index 0abd419..6910cb4 100644 --- a/lib/nbn/resources/uni.ex +++ b/lib/nbn/resources/uni.ex @@ -23,7 +23,12 @@ defmodule DiffoExample.Nbn.Uni do use Ash.Resource, fragments: [BaseInstance], - domain: Nbn + domain: Nbn, + extensions: [AshJsonApi.Resource] + + json_api do + type "uni" + end resource do description "An Ash Resource representing a User Network Interface (UNI)" @@ -111,9 +116,9 @@ defmodule DiffoExample.Nbn.Uni do # mines related resource to characteristics def mine_related(changeset, _context) when is_struct(changeset, Ash.Changeset) do - reverse_relationships = Ash.Changeset.get_attribute(changeset, :reverse_relationships) + uni = Ash.load!(changeset.data, [reverse_relationships: [:characteristics]]) - ntd_relationship = hd(reverse_relationships) + ntd_relationship = hd(uni.reverse_relationships) port = {:port, Diffo.Unwrap.unwrap(hd(ntd_relationship.characteristics).value)} {:ok, ntd} = Diffo.Provider.get_instance_by_id(ntd_relationship.source_id) diff --git a/lib/nbn/router.ex b/lib/nbn/router.ex new file mode 100644 index 0000000..c20452c --- /dev/null +++ b/lib/nbn/router.ex @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.Router do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + NBN HTTP router. Handles the catalog endpoint directly and forwards + all JSON API traffic to the AshJsonApi router. + + Start with: + + Plug.Cowboy.http(DiffoExample.Nbn.Router, [], port: 4000) + """ + use Plug.Router + + plug Plug.Parsers, + parsers: [:json], + pass: ["application/vnd.api+json", "application/json"], + json_decoder: Jason + + plug :match + plug :dispatch + + get "/catalog" do + result = Jason.encode!(DiffoExample.Nbn.Catalog.list()) + + conn + |> put_resp_content_type("application/json") + |> send_resp(200, result) + end + + forward "/", to: DiffoExample.Nbn.ApiRouter +end diff --git a/mix.exs b/mix.exs index 7f33924..614a1e5 100644 --- a/mix.exs +++ b/mix.exs @@ -81,6 +81,9 @@ defmodule DiffoExample.MixProject do defp deps do [ {:diffo, diffo_version("~> 0.2.0")}, + {:ash_json_api, "~> 1.6"}, + {:plug_cowboy, "~> 2.7"}, + {:req, "~> 0.5", only: [:dev, :test]}, {:igniter, "~> 0.6", only: [:dev, :test]}, {:ex_doc, "~> 0.37", only: [:dev, :test], runtime: false} ] diff --git a/mix.lock b/mix.lock index 04acf2a..233d094 100644 --- a/mix.lock +++ b/mix.lock @@ -1,10 +1,15 @@ %{ "ash": {:hex, :ash, "3.24.3", "f7280a43c5e64f769a450f3dd59ace6dcd73edcdd0de7599815b1b31f59292fb", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.6.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1022f8c549632137cbc8956f07bb4981405297f5abe7a752b4dffac175c3381"}, "ash_jason": {:hex, :ash_jason, "3.1.0", "84a88dfe5e25a20d55cf2d2664885cd086fa45871e8777aedc3ad96a282e2a6f", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:spark, ">= 2.1.21 and < 3.0.0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "71e6bbc421fb2cf7079f8804814145cca458116c839fc798f9606b806e07eb2b"}, + "ash_json_api": {:hex, :ash_json_api, "1.6.5", "ff925107ebdced10407a6045dc3ff9e8335fe3485ce042f899817a2b47f49b5f", [:mix], [{:ash, ">= 3.19.1 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.58 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:json_xema, "~> 0.4", [hex: :json_xema, repo: "hexpm", optional: false]}, {:open_api_spex, "~> 3.16", [hex: :open_api_spex, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:plug, "~> 1.11", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, ">= 2.2.10", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "ab2f413d977a560843bbf7a7f6bc486b74e944ef51d9adf93c355a4bf984b0df"}, "ash_neo4j": {:hex, :ash_neo4j, "0.3.1", "52b81e870d020815ffb2699f3fa207e10e909418e80c8aec4c64ed668418299a", [:mix], [{:ash, ">= 3.24.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:bolty, ">= 0.0.10", [hex: :bolty, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "5da556d93e03fda97e1bb626941114b7011a64173b1c10deb12cf66523e82001"}, "ash_outstanding": {:hex, :ash_outstanding, "0.2.4", "c72b91f1b8e4859fb033eddf66d0ba36cfd8af0c2a9748c7ef9e6ccfdb5d093d", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:outstanding, "~> 0.2.4", [hex: :outstanding, repo: "hexpm", optional: false]}], "hexpm", "64ba8f582ce69c9050352c75f0895db186c7a56f35039dab34c8e1ab7516f9ce"}, "ash_state_machine": {:hex, :ash_state_machine, "0.2.13", "e1c368ebf01ef73477739ee76d53e513d073b141ec11e7bf7f91d8f2d8fc9569", [:mix], [{:ash, ">= 3.4.66 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}], "hexpm", "aa21c92a8950850df69b5205bf41efc1e502f5ab839425ba08561f0421c9f226"}, "bolty": {:hex, :bolty, "0.0.10", "ec88948d30cfc213cdb1168f86d96cdcadd80f16e4f29701966e69dfbac43ded", [:mix], [{:db_connection, "~> 2.7.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "2ce63d6c23301d1c9a61fd29ef06ebb7d2e775d4fd4144e86c2717aa43f409c9"}, + "conv_case": {:hex, :conv_case, "0.2.3", "c1455c27d3c1ffcdd5f17f1e91f40b8a0bc0a337805a6e8302f441af17118ed8", [:mix], [], "hexpm", "88f29a3d97d1742f9865f7e394ed3da011abb7c5e8cc104e676fdef6270d4b4a"}, + "cowboy": {:hex, :cowboy, "2.14.2", "4008be1df6ade45e4f2a4e9e2d22b36d0b5aba4e20b0a0d7049e28d124e34847", [:make, :rebar3], [{:cowlib, ">= 2.16.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "569081da046e7b41b5df36aa359be71a0c8874e5b9cff6f747073fc57baf1ab9"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, + "cowlib": {:hex, :cowlib, "2.16.0", "54592074ebbbb92ee4746c8a8846e5605052f29309d3a873468d76cdf932076f", [:make, :rebar3], [], "hexpm", "7f478d80d66b747344f0ea7708c187645cfcc08b11aa424632f78e25bf05db51"}, "crux": {:hex, :crux, "0.1.2", "4441c9e3a34f1e340954ce96b9ad5a2de13ceb4f97b3f910211227bb92e2ca90", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "563ea3748ebfba9cc078e6d198a1d6a06015a8fae503f0b721363139f0ddb350"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, @@ -19,6 +24,7 @@ "igniter": {:hex, :igniter, "0.7.9", "8c573440b8127fd80be8220fb197e7422317a81072054fcc0b336029f035a416", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "123513d09f3af149db851aad8492b5b49f861d2c466a72031b2a0cbd9f45526f"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "json_xema": {:hex, :json_xema, "0.6.5", "060459c9c9152650edb4427b1acbc61fa43a23bcea0301d200cafa76e0880f37", [:mix], [{:conv_case, "~> 0.2", [hex: :conv_case, repo: "hexpm", optional: false]}, {:xema, "~> 0.16", [hex: :xema, repo: "hexpm", optional: false]}], "hexpm", "b8ffdbc2f67aa8b91b44e1ba0ab77eb5c0b0142116f8fbb804977fb939d470ef"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, @@ -30,6 +36,13 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "outstanding": {:hex, :outstanding, "0.2.5", "2f40416eb9617748cb1f8ae4c8ed94515d731f9c4fcee4f902355d30bc0792cc", [:mix], [], "hexpm", "bb47a210f0d2804ea6b8477fa6f4d15e8c58c18acee79d8e06c9296e6dd004cd"}, "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, + "phoenix": {:hex, :phoenix, "1.8.5", "919db335247e6d4891764dc3063415b0d2457641c5f9b3751b5df03d8e20bbcf", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "83b2bb125127e02e9f475c8e3e92736325b5b01b0b9b05407bcb4083b7a32485"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, + "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.8.0", "07789e9c03539ee51bb14a07839cc95aa96999fd8846ebfd28c97f0b50c7b612", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9cbfaaf17463334ca31aed38ea7e08a68ee37cabc077b1e9be6d2fb68e0171d0"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, + "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, "reactor": {:hex, :reactor, "1.0.1", "ca3b5cf3c04ec8441e67ea2625d0294939822060b1bfd00ffdaaf75b7682d991", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "3497db2b204c9a3cabdaf1b26d2405df1dfbb138ce0ce50e616e9db19fec0043"}, "req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"}, "rewrite": {:hex, :rewrite, "1.3.0", "67448ba7975690b35ba7e7f35717efcce317dbd5963cb0577aa7325c1923121a", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "d111ac7ff3a58a802ef4f193bbd1831e00a9c57b33276e5068e8390a212714a5"}, @@ -41,6 +54,9 @@ "telemetry": {:hex, :telemetry, "1.4.1", "ab6de178e2b29b58e8256b92b382ea3f590a47152ca3651ea857a6cae05ac423", [:rebar3], [], "hexpm", "2172e05a27531d3d31dd9782841065c50dd5c3c7699d95266b2edd54c2dafa1c"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"}, + "xema": {:hex, :xema, "0.17.7", "7eeda174b70a5f7fb1cc2e9fa3a7d4e78e206a99866c107d477309410b678cf2", [:mix], [{:conv_case, "~> 0.2.2", [hex: :conv_case, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "7e3d7c0629282c21af6aaa5e2ba593218cd764a57bd1ae49e2c4412324e904cd"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yaml_elixir": {:hex, :yaml_elixir, "2.12.1", "d74f2d82294651b58dac849c45a82aaea639766797359baff834b64439f6b3f4", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "d9ac16563c737d55f9bfeed7627489156b91268a3a21cd55c54eb2e335207fed"}, "ymlr": {:hex, :ymlr, "5.1.5", "0b9207c7940be3f2bc29b77cd55109d5aa2f4dcde6575942017335769e6f5628", [:mix], [], "hexpm", "7030cb240c46850caeb3b01be745307632be319b15f03083136f6251f49b516d"}, From 61f8a3742c55ebf0859fd78c8a68f7c9c9f495c9 Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Sun, 26 Apr 2026 06:12:38 +0930 Subject: [PATCH 11/13] =?UTF-8?q?stop=20tracking=20.claude/settings.json?= =?UTF-8?q?=20=E2=80=94=20already=20in=20.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .claude/settings.json diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index e421007..0000000 --- a/.claude/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(mix deps.get)", - "Bash(git -C /Users/beanlanda/git/diffo_example status)", - "Read(//Users/beanlanda/git/**)", - "Read(//Users/beanlanda/.mix/**)" - ] - } -} From a19c3f54d552f102ca76f28b3455b1a90c6a9651 Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Sun, 26 Apr 2026 09:47:29 +0930 Subject: [PATCH 12/13] add RSP multi-tenancy, JSON API, and NBN domain documentation for v0.2.0 - RSP resource with AshStateMachine lifecycle (inactive/active/suspended) - Ash Policy multi-tenancy: SetRspId change, OwnedByActor and NoActor checks, RspOwnership macro shared across 5 RSP-owned resources - NTD and UNI are NBN-owned infrastructure: readable by any RSP, mutable only by nil actor - JSON API via AshJsonApi and Plug.Cowboy on port 4000 - RSP list action with epid sort; field_policy restricts state visibility to record owner - Livebook moved to documentation/domains/diffo_example_nbn.livemd with Kino RSP selector and actor-scoped provisioning flow - documentation/domains/nbn.md: Perentie ecosystem narrative and RSP spirit animals - README updated to describe both NBN and Access domains --- .gitignore | 5 +- README.md | 23 +-- .../domains/diffo_example_nbn.livemd | 131 ++++++++------- documentation/domains/nbn.md | 23 +++ lib/diffo_example/application.ex | 11 +- lib/nbn/changes/set_rsp_id.ex | 13 ++ lib/nbn/checks/no_actor.ex | 15 ++ lib/nbn/checks/owned_by_actor.ex | 19 +++ lib/nbn/initializer.ex | 39 ++++- lib/nbn/nbn.ex | 15 ++ lib/nbn/resources/avc.ex | 15 +- lib/nbn/resources/cvc.ex | 15 +- lib/nbn/resources/nbn_ethernet.ex | 15 +- lib/nbn/resources/nni.ex | 15 +- lib/nbn/resources/nni_group.ex | 15 +- lib/nbn/resources/ntd.ex | 17 +- lib/nbn/resources/rsp.ex | 152 ++++++++++++++++++ lib/nbn/resources/uni.ex | 17 +- lib/nbn/rsp_ownership.ex | 37 +++++ mix.exs | 10 +- mix.lock | 3 + test/nbn/rsp_test.exs | 132 +++++++++++++++ 22 files changed, 657 insertions(+), 80 deletions(-) rename diffo_example.livemd => documentation/domains/diffo_example_nbn.livemd (69%) create mode 100644 documentation/domains/nbn.md create mode 100644 lib/nbn/changes/set_rsp_id.ex create mode 100644 lib/nbn/checks/no_actor.ex create mode 100644 lib/nbn/checks/owned_by_actor.ex create mode 100644 lib/nbn/resources/rsp.ex create mode 100644 lib/nbn/rsp_ownership.ex create mode 100644 test/nbn/rsp_test.exs diff --git a/.gitignore b/.gitignore index 03f6fe6..9481f4e 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,7 @@ diffo-*.tar /.elixir_ls -.DS_Store \ No newline at end of file +.DS_Store + +# Agent related +.claude/* \ No newline at end of file diff --git a/README.md b/README.md index adddd0e..0ffb9dc 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,24 @@ SPDX-License-Identifier: MIT [![Module Version](https://img.shields.io/hexpm/v/diffo)](https://hex.pm/packages/diffo_example) [![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen)](https://hexdocs.pm/diffo_example/) -[![Run in Livebook](https://livebook.dev/badge/v1/blue.svg)](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fdiffo-dev%2Fdiffo_example%2Fblob%2Fmain%2Fdiffo_example.livemd) [![License](https://img.shields.io/hexpm/l/diffo)](https://github.com/diffo-dev/diffo_example/blob/master/LICENSES/MIT.md) [![REUSE status](https://api.reuse.software/badge/github.com/diffo-dev/diffo_example)](https://api.reuse.software/info/github.com/diffo-dev/diffo_example) -This repo contains Diffo Examples. - [Diffo](https://github.com/diffo-dev/diffo) is a Telecommunications Management Forum (TMF) Service and Resource Manager, built for autonomous networks. +This repo contains two independent example domains, each modelling a different slice of a telco network. + +## NBN Domain + +A declarative model of a fictional NBN Ethernet access hierarchy — NbnEthernet, UNI, AVC, NTD, CVC, NNI Group, and NNI — built entirely with the Diffo Provider Instance DSL. Includes multi-tenancy via Ash Policy: each RSP can only see and manage the resources they own. + +[![Run in Livebook](https://livebook.dev/badge/v1/blue.svg)](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fdiffo-dev%2Fdiffo_example%2Fblob%2Fdev%2Fdocumentation%2Fdomains%2Fdiffo_example_nbn.livemd) + +The livebook walks through provisioning a complete NBN Ethernet access circuit, selecting an RSP to operate as, and demonstrating how the `mine` actions propagate technology, speeds, CVLAN, and port assignments up the resource hierarchy. + +## Access Domain + +A copper-network equivalent covering DSL access services — Cable, Card, Path, and Shelf. Explore `lib/access/` for the domain model. ## Installation @@ -32,13 +42,6 @@ end You need [Neo4j](https://github.com/neo4j/neo4j) available. We recommend the Neo4j Community 5 latest, available at [Neo4j Deploymnent Centre](https://neo4j.com/deployment-center/) which can be installed locally. You can also configure connection to a cloud based database service such as [Neo4j AuraDB](https://neo4j.com/product/auradb/). -## Tutorial - -Click the **Run in Livebook** badge above to open the interactive tutorial, or find it at [diffo_example.livemd](diffo_example.livemd). - -The diffo_example livebook walks through provisioning a complete NBN Ethernet access circuit — NTD, UNI, AVC, CVC, NNI Group, and NNI — showing how the `mine` actions propagate technology, speeds, CVLAN, and port assignments up the resource hierarchy. - - ## Contributions Contributions are welcome, please start with an [issue](https://github.com/diffo-dev/diffo_example/issues) diff --git a/diffo_example.livemd b/documentation/domains/diffo_example_nbn.livemd similarity index 69% rename from diffo_example.livemd rename to documentation/domains/diffo_example_nbn.livemd index 830be14..df77a32 100644 --- a/diffo_example.livemd +++ b/documentation/domains/diffo_example_nbn.livemd @@ -10,20 +10,24 @@ SPDX-License-Identifier: MIT Mix.install( [ {:diffo_example, "~> 0.2.0"}, + {:kino, "~> 0.14"}, {:req, "~> 0.5"} ], - config: [ - bolty: [{Bolt, [ - uri: "bolt://localhost:7687", - auth: [username: "neo4j", password: "password"], - user_agent: "diffoExampleLivebook/1", - pool_size: 15, - max_overflow: 3, - prefix: :default, - name: Bolt, - log: false, - log_hex: false - ]}] + config: [ + bolty: [ + {Bolt, + [ + uri: "bolt://localhost:7687", + auth: [username: "neo4j", password: "password"], + user_agent: "diffoExampleLivebook/1", + pool_size: 15, + max_overflow: 3, + prefix: :default, + name: Bolt, + log: false, + log_hex: false + ]} + ] ], consolidate_protocols: false ) @@ -67,42 +71,28 @@ It is helpful to have a Neo4j browser open locally, typically at http://localhos AshNeo4j.Neo4jHelper.delete_all() ``` -## Setup Aliases - -```elixir -require Ash.Query -alias DiffoExample.Nbn -alias DiffoExample.Nbn.NbnEthernet -alias DiffoExample.Nbn.Uni -alias DiffoExample.Nbn.Avc -alias DiffoExample.Nbn.Ntd -alias DiffoExample.Nbn.Cvc -alias DiffoExample.Nbn.NniGroup -alias DiffoExample.Nbn.Nni -alias DiffoExample.Nbn.Technology -alias DiffoExample.Nbn.Speeds -import Jason, only: [encode: 2] -``` - -## About NBN +## About NBN Co NBN (National Broadband Network) is Australia's wholesale fixed-line access network, operated by NBN Co. It provides standardised access products to Retail Service Providers (RSPs), who in turn deliver internet and other services to end customers. +For the purpose of this example we are going to refer to a simplified, and re-imagined NBN Co as NBN. + An RSP typically combines: * An **NBN Ethernet** access circuit (UNI + AVC) at the customer premises — the access and aggregation layer modelled in this domain * A **home gateway** device installed at the UNI, which provides the customer's LAN, Wi-Fi, and sometimes voice * Transport, aggregation, and edge infrastructure connecting the NNI to the RSP's network and on to the internet -NBN Co connects the customer premises to the RSP's network via a Point of Interconnect (POI). The NNI sits at the POI, grouped into NNI Groups. AVCs carrying customer traffic are aggregated onto a CVC, which terminates at the NNI Group. The RSP purchases CVC capacity to carry the aggregate traffic of its customers at that POI. +NBN connects the customer premises to the RSP's network via a Point of Interconnect (POI). The NNI sits at the POI, grouped into NNI Groups. AVCs carrying customer traffic are aggregated onto a CVC, which terminates at the NNI Group. The RSP purchases CVC capacity to carry the aggregate traffic of its customers at that POI. -NBN is delivered over several access technologies — FTTP, FTTN, FTTB, FTTC, HFC, Fixed Wireless, and Satellite — which determine which bandwidth profiles and speeds are available to a given premises. +NBN delivers over several access technologies — FTTP, FTTN, FTTB, FTTC, HFC, Fixed Wireless, and Satellite — which determine which bandwidth profiles and speeds are available to a given premises. -## Technology and Speeds +## NBN Ethernet Technology and Speeds The NBN domain defines Technology as an Ash Enum covering all NBN access types: ```elixir +alias DiffoExample.Nbn.{Technology,Speeds} Technology.values() ``` @@ -125,21 +115,51 @@ Speeds.speeds(:wireless_superfast, :FixedWireless) Speeds.speeds(:home_fast, :FixedWireless) ``` -## Building the Network Hinterland +## Multi-tenancy + +Each RSP operates in isolation — they can only see and manage the resources they own. This multi-tenancy is enforced at the Ash policy layer: every NBN resource is stamped with the owning RSP's id at creation, and subsequent reads, updates, and destroys are scoped to the record owner. + +Select the RSP you want to operate as for the rest of this livebook. All resources you build will be owned by that RSP and isolated from resources owned by others. + +```elixir +alias DiffoExample.Nbn +alias DiffoExample.Nbn.Rsp +import Jason, only: [encode: 2] +DiffoExample.Nbn.Initializer.init() +rsps = Nbn.list_rsps!() +Kino.DataTable.new(rsps, keys: [:epid, :name, :short_name, :state]) +``` + +```elixir +rsp_input = Kino.Input.select( + "Operate as RSP", + Enum.map(rsps, fn rsp -> {rsp.name, Atom.to_string(rsp.short_name)} end) +) +``` + +```elixir +actor = Enum.find(rsps, fn rsp -> rsp.name == Kino.Input.read(rsp_input) end) +actor +``` + +## Maintaining Shareable Resources + +As an RSP we need maintain some shareable network resources: NNI, NNI Group, and CVC. -Before we can provision an NBN Ethernet access we need the shared network resources: NNI, NNI Group, and CVC. +We'll need these everywhere we operate, in advance of and sufficient for all the NBN Ethernet Accesses we have. We'll just build one of each right now. -Build an NNI — the physical interconnect between the RSP and NBN Co: +Build an NNI — the physical interconnect between the RSP and NBN: ```elixir -nni = Nbn.build_nni!(%{}) +alias DiffoExample.Nbn.{Nni, NniGroup, CVC} +nni = Nbn.build_nni!(%{}, actor: actor) nni |> Jason.encode!(pretty: true) |> IO.puts ``` Build an NNI Group — a logical grouping of NNIs at a point of interconnect: ```elixir -nni_group = Nbn.build_nni_group!(%{}) +nni_group = Nbn.build_nni_group!(%{}, actor: actor) nni_group |> Jason.encode!(pretty: true) |> IO.puts ``` @@ -148,30 +168,33 @@ Define the NNI Group with an SVLAN assignment and relate the NNI: ```elixir nni_group = Nbn.define_nni_group!(nni_group, %{ characteristic_value_updates: [nni_group: [svlan: 100]] -}) +}, actor: actor) nni_group = Nbn.relate_nni_group!(nni_group, %{ relationships: [%Diffo.Provider.Instance.Relationship{id: nni.id, alias: :nni, type: :isAssigned}] -}) +}, actor: actor) nni_group |> Jason.encode!(pretty: true) |> IO.puts ``` Build a CVC — the aggregation virtual circuit that terminates at the NNI Group: ```elixir -cvc = Nbn.build_cvc!(%{}) +cvc = Nbn.build_cvc!(%{}, actor: actor) cvc = Nbn.relate_cvc!(cvc, %{ relationships: [%Diffo.Provider.Instance.Relationship{id: nni_group.id, alias: :nni_group, type: :isAssigned}] -}) +}, actor: actor) cvc |> Jason.encode!(pretty: true) |> IO.puts ``` -## Provisioning an NBN Ethernet Access +## Provisioning NBN Ethernet + +For each customer site we want to provide service to, we need an NBN Ethernet composite resource, involving an NTD, UNI, AVC and CVC. -With the hinterland in place we can provision a customer-facing NBN Ethernet access. +The NTD is NBN infrastructure — built and managed by NBN, visible to any RSP. It may not exist at a new or existing customer site, so may be built on demand by NBN. Build an NTD — the device installed at the customer premises: ```elixir +alias DiffoExample.Nbn.{Ntd, Uni, Avc, NbnEthernet} ntd = Nbn.build_ntd!(%{}) ntd = Nbn.define_ntd!(ntd, %{ characteristic_value_updates: [ntd: [technology: :FTTP, ports: [1, 2, 3, 4]]] @@ -203,28 +226,28 @@ uni |> Jason.encode!(pretty: true) |> IO.puts Build an AVC and assign it a CVLAN from the CVC: ```elixir -avc = Nbn.build_avc!(%{}) +avc = Nbn.build_avc!(%{}, actor: actor) avc = Nbn.define_avc!(avc, %{ characteristic_value_updates: [avc: [bandwidth_profile: :home_ultrafast]] -}) +}, actor: actor) cvc = Nbn.assign_cvlan!(cvc, %{ assignment: %Assignment{assignee_id: avc.id, operation: :auto_assign} -}) -avc = Nbn.mine_avc!(avc, %{}) +}, actor: actor) +avc = Nbn.mine_avc!(avc, %{}, actor: actor) avc |> Jason.encode!(pretty: true) |> IO.puts ``` Now build the top-level NBN Ethernet access and relate it to both the UNI and AVC: ```elixir -pri = Nbn.build_nbn_ethernet!(%{}) +pri = Nbn.build_nbn_ethernet!(%{}, actor: actor) pri = Nbn.relate_nbn_ethernet!(pri, %{ relationships: [ %Diffo.Provider.Instance.Relationship{id: uni.id, alias: :uni, type: :isAssigned}, %Diffo.Provider.Instance.Relationship{id: avc.id, alias: :avc, type: :isAssigned} ] -}) -pri = Nbn.mine_nbn_ethernet!(pri, %{}) +}, actor: actor) +pri = Nbn.mine_nbn_ethernet!(pri, %{}, actor: actor) pri |> Jason.encode!(pretty: true) |> IO.puts ``` @@ -251,19 +274,19 @@ The NBN domain exposes a JSON API via `Plug.Cowboy` on port 4000. Start the serv First check the catalog — all NBN specifications are initialised on startup: ```elixir -Req.get!("http://localhost:4000/catalog").body |> Jason.encode!(pretty: true) |> IO.puts() +Req.get!("http://localhost:4000/catalog", decode_body: false).body |> IO.puts() ``` Now retrieve all NBN Ethernet instances: ```elixir -Req.get!("http://localhost:4000/nbnEthernet").body |> Jason.encode!(pretty: true) |> IO.puts() +Req.get!("http://localhost:4000/nbnEthernet", decode_body: false).body |> IO.puts() ``` Or fetch the one we provisioned above by id: ```elixir -Req.get!("http://localhost:4000/nbnEthernet/#{pri.id}").body |> Jason.encode!(pretty: true) |> IO.puts() +Req.get!("http://localhost:4000/nbnEthernet/#{pri.id}", decode_body: false).body |> IO.puts() ``` ## What Next? diff --git a/documentation/domains/nbn.md b/documentation/domains/nbn.md new file mode 100644 index 0000000..eb3bcc0 --- /dev/null +++ b/documentation/domains/nbn.md @@ -0,0 +1,23 @@ + + +# The NBN Domain + +## The Perentie Ecosystem + +NBN Co operates as **Perentie** — Australia's largest monitor lizard, ancient and continent-wide. Perentie owns the territory. It does not compete with the animals moving through its country; it simply defines the ground they all walk on. + +The RSPs are the spirit animals of the ecosystem, each finding their niche in Perentie's range: + +| RSP | Spirit Animal | Inspiration | +| ------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------ | +| Wedge-tail Telecom | Wedge-tailed Eagle | Australia's apex aerial predator — dominant, territorial, commands every landscape it surveys | +| Quokka Connect | Quokka | Famously friendly, genuinely Australian, radiates good energy — operates in WA under bilateral agreement with Perentie | +| Ibis Telecom | White Ibis | Beloved in spite of its reputation, scrappy, surprisingly capable | +| Taipan Group | Taipan | Carries the TPG initials; fast, precise, not to be underestimated | +| Echidna Networks | Echidna | Prickly on the surface, uniquely capable beneath it | +| Dugong Digital | Dugong | Slow and steady, but still very much alive | +| Lyrebird | Lyrebird | Mimics everything, loops back on itself, endlessly clever | diff --git a/lib/diffo_example/application.ex b/lib/diffo_example/application.ex index 8bca8f1..0de7553 100644 --- a/lib/diffo_example/application.ex +++ b/lib/diffo_example/application.ex @@ -9,10 +9,13 @@ defmodule DiffoExample.Application do @impl true def start(_type, _args) do - children = [ - {Plug.Cowboy, scheme: :http, plug: DiffoExample.Nbn.Router, options: [port: 4000]}, - {Task, &DiffoExample.Nbn.Initializer.init/0} - ] + children = + [ + {Task, &DiffoExample.Nbn.Initializer.init/0} + ] ++ + if Mix.env() == :test, + do: [], + else: [{Plug.Cowboy, scheme: :http, plug: DiffoExample.Nbn.Router, options: [port: 4000]}] Supervisor.start_link(children, strategy: :one_for_one, name: DiffoExample.Supervisor) end diff --git a/lib/nbn/changes/set_rsp_id.ex b/lib/nbn/changes/set_rsp_id.ex new file mode 100644 index 0000000..c03dc03 --- /dev/null +++ b/lib/nbn/changes/set_rsp_id.ex @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.Changes.SetRspId do + use Ash.Resource.Change + + def change(changeset, _opts, %{actor: %{id: id}}) do + Ash.Changeset.force_change_attribute(changeset, :rsp_id, id) + end + + def change(changeset, _opts, _context), do: changeset +end diff --git a/lib/nbn/checks/no_actor.ex b/lib/nbn/checks/no_actor.ex new file mode 100644 index 0000000..e40e12c --- /dev/null +++ b/lib/nbn/checks/no_actor.ex @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.Checks.NoActor do + @moduledoc false + use Ash.Policy.SimpleCheck + + @impl true + def describe(_opts), do: "no actor present (internal Perentie call)" + + @impl true + def match?(nil, _context, _opts), do: true + def match?(_actor, _context, _opts), do: false +end diff --git a/lib/nbn/checks/owned_by_actor.ex b/lib/nbn/checks/owned_by_actor.ex new file mode 100644 index 0000000..f53f0fa --- /dev/null +++ b/lib/nbn/checks/owned_by_actor.ex @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.Checks.OwnedByActor do + @moduledoc false + use Ash.Policy.FilterCheck + + @impl true + def describe(_opts), do: "actor owns resource (rsp_id matches actor id)" + + @impl true + def filter(actor, _context, _opts) do + case actor do + %{id: id} -> [rsp_id: id] + _ -> false + end + end +end diff --git a/lib/nbn/initializer.ex b/lib/nbn/initializer.ex index d261b87..13b80e5 100644 --- a/lib/nbn/initializer.ex +++ b/lib/nbn/initializer.ex @@ -6,14 +6,26 @@ defmodule DiffoExample.Nbn.Initializer do @moduledoc """ Diffo - TMF Service and Resource Management with a difference - Initializes the NBN domain's specifications in the catalog on application startup, - so the catalog is populated before any instances are built. + Initializes the NBN domain on application startup: + - upserts all resource specifications into the catalog + - seeds RSP records in historical EPID sequence """ alias Diffo.Provider.Instance.Specification + alias DiffoExample.Nbn + + @rsps [ + %{name: "Wedge-tail Telecom", short_name: :wedgetail, epid: "0001"}, + %{name: "Quokka Connect", short_name: :quokka, epid: "0002"}, + %{name: "Ibis Telecom", short_name: :ibis, epid: "0003"}, + %{name: "Taipan Group", short_name: :taipan, epid: "0004"}, + %{name: "Echidna Networks", short_name: :echidna, epid: "0005"}, + %{name: "Dugong Digital", short_name: :dugong, epid: "0006"}, + %{name: "Lyrebird", short_name: :lyrebird, epid: "0007"} + ] def init do - DiffoExample.Nbn + Nbn |> Ash.Domain.Info.resources() |> Enum.each(fn module -> try do @@ -22,5 +34,26 @@ defmodule DiffoExample.Nbn.Initializer do _ -> :ok end end) + + seed_rsps() + end + + defp seed_rsps do + Enum.each(@rsps, fn attrs -> + try do + case Nbn.get_rsp_by_epid(attrs.epid) do + {:ok, nil} -> seed_rsp(attrs) + {:ok, _} -> :ok + {:error, _} -> seed_rsp(attrs) + end + rescue + e -> require Logger; Logger.error("Exception seeding RSP #{attrs.epid}: #{inspect(e)}") + end + end) + end + + defp seed_rsp(attrs) do + {:ok, rsp} = Nbn.create_rsp(attrs) + {:ok, _} = Nbn.activate_rsp(rsp) end end diff --git a/lib/nbn/nbn.ex b/lib/nbn/nbn.ex index b739947..43d4bfa 100644 --- a/lib/nbn/nbn.ex +++ b/lib/nbn/nbn.ex @@ -23,6 +23,7 @@ defmodule DiffoExample.Nbn do alias DiffoExample.Nbn.Cvc alias DiffoExample.Nbn.NniGroup alias DiffoExample.Nbn.Nni + alias DiffoExample.Nbn.Rsp domain do description "An example showing how TMF Resources for a fictional NBN domain can be extended from the Provider domain" @@ -97,6 +98,10 @@ defmodule DiffoExample.Nbn do delete :destroy end + base_route "/rsp", Rsp do + get :read + end + end end @@ -157,5 +162,15 @@ defmodule DiffoExample.Nbn do define :relate_nni, action: :relate end + resource Rsp do + define :list_rsps, action: :list + define :get_rsp_by_epid, action: :read, get_by: :epid + define :get_rsp_by_short_name, action: :read, get_by: :short_name + define :create_rsp, action: :create + define :activate_rsp, action: :activate + define :suspend_rsp, action: :suspend + define :deactivate_rsp, action: :deactivate + end + end end diff --git a/lib/nbn/resources/avc.ex b/lib/nbn/resources/avc.ex index 9924932..5f49871 100644 --- a/lib/nbn/resources/avc.ex +++ b/lib/nbn/resources/avc.ex @@ -22,7 +22,8 @@ defmodule DiffoExample.Nbn.Avc do use Ash.Resource, fragments: [BaseInstance], domain: Nbn, - extensions: [AshJsonApi.Resource] + extensions: [AshJsonApi.Resource], + authorizers: [Ash.Policy.Authorizer] json_api do type "avc" @@ -46,6 +47,14 @@ defmodule DiffoExample.Nbn.Avc do characteristic :cvc, DiffoExample.Nbn.CvcValue end + attributes do + attribute :rsp_id, :uuid do + description "the owning RSP's id — nil for Perentie-managed infrastructure" + allow_nil? true + public? true + end + end + actions do create :build do description "creates a new AVC resource instance" @@ -67,6 +76,8 @@ defmodule DiffoExample.Nbn.Avc do ActionHelper.build_after(changeset, result, Nbn, :get_avc_by_id) end) + change DiffoExample.Nbn.Changes.SetRspId + change load [:href] upsert? false end @@ -121,4 +132,6 @@ defmodule DiffoExample.Nbn.Avc do Ash.Changeset.force_set_argument(changeset, :characteristic_value_updates, avc: [cvlan]) end + + use DiffoExample.Nbn.RspOwnership end diff --git a/lib/nbn/resources/cvc.ex b/lib/nbn/resources/cvc.ex index 81d67a2..e6b8215 100644 --- a/lib/nbn/resources/cvc.ex +++ b/lib/nbn/resources/cvc.ex @@ -24,7 +24,8 @@ defmodule DiffoExample.Nbn.Cvc do use Ash.Resource, fragments: [BaseInstance], domain: Nbn, - extensions: [AshJsonApi.Resource] + extensions: [AshJsonApi.Resource], + authorizers: [Ash.Policy.Authorizer] json_api do type "cvc" @@ -50,6 +51,14 @@ defmodule DiffoExample.Nbn.Cvc do characteristic :cvlans, Diffo.Provider.AssignableValue end + attributes do + attribute :rsp_id, :uuid do + description "the owning RSP's id — nil for Perentie-managed infrastructure" + allow_nil? true + public? true + end + end + actions do create :build do description "creates a new CVC resource instance" @@ -71,6 +80,8 @@ defmodule DiffoExample.Nbn.Cvc do ActionHelper.build_after(changeset, result, Nbn, :get_cvc_by_id) end) + change DiffoExample.Nbn.Changes.SetRspId + change load [:href] upsert? false end @@ -128,6 +139,8 @@ defmodule DiffoExample.Nbn.Cvc do DiffoExample.Nbn.Util.identifier("CVC") end + use DiffoExample.Nbn.RspOwnership + # mines related resource to characteristics def mine_related(changeset, _context) when is_struct(changeset, Ash.Changeset) do reverse_relationships = Ash.Changeset.get_attribute(changeset, :reverse_relationships) diff --git a/lib/nbn/resources/nbn_ethernet.ex b/lib/nbn/resources/nbn_ethernet.ex index 26e37ca..d5c2ec2 100644 --- a/lib/nbn/resources/nbn_ethernet.ex +++ b/lib/nbn/resources/nbn_ethernet.ex @@ -23,7 +23,8 @@ defmodule DiffoExample.Nbn.NbnEthernet do use Ash.Resource, fragments: [BaseInstance], domain: Nbn, - extensions: [AshJsonApi.Resource] + extensions: [AshJsonApi.Resource], + authorizers: [Ash.Policy.Authorizer] json_api do type "nbnEthernet" @@ -50,6 +51,14 @@ defmodule DiffoExample.Nbn.NbnEthernet do # end end + attributes do + attribute :rsp_id, :uuid do + description "the owning RSP's id — nil for Perentie-managed infrastructure" + allow_nil? true + public? true + end + end + actions do create :build do description "creates a new NBN Ethernet access resource instance" @@ -71,6 +80,8 @@ defmodule DiffoExample.Nbn.NbnEthernet do ActionHelper.build_after(changeset, result, Nbn, :get_nbn_ethernet_by_id) end) + change DiffoExample.Nbn.Changes.SetRspId + change load [:href] upsert? false end @@ -165,4 +176,6 @@ defmodule DiffoExample.Nbn.NbnEthernet do (Atom.to_string(alias) <> "id") |> String.to_atom() end + + use DiffoExample.Nbn.RspOwnership end diff --git a/lib/nbn/resources/nni.ex b/lib/nbn/resources/nni.ex index e9b84f6..a3682ea 100644 --- a/lib/nbn/resources/nni.ex +++ b/lib/nbn/resources/nni.ex @@ -23,7 +23,8 @@ defmodule DiffoExample.Nbn.Nni do use Ash.Resource, fragments: [BaseInstance], domain: Nbn, - extensions: [AshJsonApi.Resource] + extensions: [AshJsonApi.Resource], + authorizers: [Ash.Policy.Authorizer] json_api do type "nni" @@ -46,6 +47,14 @@ defmodule DiffoExample.Nbn.Nni do characteristic :nni, DiffoExample.Nbn.NniValue end + attributes do + attribute :rsp_id, :uuid do + description "the owning RSP's id — nil for Perentie-managed infrastructure" + allow_nil? true + public? true + end + end + actions do create :build do description "creates a new NNI resource instance" @@ -67,6 +76,8 @@ defmodule DiffoExample.Nbn.Nni do ActionHelper.build_after(changeset, result, Nbn, :get_nni_by_id) end) + change DiffoExample.Nbn.Changes.SetRspId + change load [:href] upsert? false end @@ -97,4 +108,6 @@ defmodule DiffoExample.Nbn.Nni do DiffoExample.Nbn.Util.identifier("NNI") end end + + use DiffoExample.Nbn.RspOwnership end diff --git a/lib/nbn/resources/nni_group.ex b/lib/nbn/resources/nni_group.ex index 9e89a33..7f60ac1 100644 --- a/lib/nbn/resources/nni_group.ex +++ b/lib/nbn/resources/nni_group.ex @@ -25,7 +25,8 @@ defmodule DiffoExample.Nbn.NniGroup do use Ash.Resource, fragments: [BaseInstance], domain: Nbn, - extensions: [AshJsonApi.Resource] + extensions: [AshJsonApi.Resource], + authorizers: [Ash.Policy.Authorizer] json_api do type "nniGroup" @@ -49,6 +50,14 @@ defmodule DiffoExample.Nbn.NniGroup do characteristic :svlans, Diffo.Provider.AssignableValue end + attributes do + attribute :rsp_id, :uuid do + description "the owning RSP's id — nil for Perentie-managed infrastructure" + allow_nil? true + public? true + end + end + actions do create :build do description "creates a new NNI Group resource instance" @@ -68,6 +77,8 @@ defmodule DiffoExample.Nbn.NniGroup do ActionHelper.build_after(changeset, result, Nbn, :get_nni_group_by_id) end) + change DiffoExample.Nbn.Changes.SetRspId + change load [:href] upsert? false end @@ -105,4 +116,6 @@ defmodule DiffoExample.Nbn.NniGroup do end) end end + + use DiffoExample.Nbn.RspOwnership end diff --git a/lib/nbn/resources/ntd.ex b/lib/nbn/resources/ntd.ex index c7a66e5..a7a4da6 100644 --- a/lib/nbn/resources/ntd.ex +++ b/lib/nbn/resources/ntd.ex @@ -24,7 +24,8 @@ defmodule DiffoExample.Nbn.Ntd do use Ash.Resource, fragments: [BaseInstance], domain: Nbn, - extensions: [AshJsonApi.Resource] + extensions: [AshJsonApi.Resource], + authorizers: [Ash.Policy.Authorizer] json_api do type "ntd" @@ -110,4 +111,18 @@ defmodule DiffoExample.Nbn.Ntd do def identifier() do DiffoExample.Nbn.Util.identifier("NTD") end + + policies do + bypass DiffoExample.Nbn.Checks.NoActor do + authorize_if always() + end + + bypass actor_attribute_equals(:role, :admin) do + authorize_if always() + end + + policy action_type(:read) do + authorize_if always() + end + end end diff --git a/lib/nbn/resources/rsp.ex b/lib/nbn/resources/rsp.ex new file mode 100644 index 0000000..1f4fd66 --- /dev/null +++ b/lib/nbn/resources/rsp.ex @@ -0,0 +1,152 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.Rsp do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + Rsp - Retail Service Provider + + An RSP is a licensed provider operating within the Perentie ecosystem. + Each RSP is assigned an EPID (four-digit regulator-assigned identifier) + and a short_name atom used as their actor identity for authorisation. + """ + + alias DiffoExample.Nbn + + use Ash.Resource, + domain: Nbn, + data_layer: AshNeo4j.DataLayer, + authorizers: [Ash.Policy.Authorizer], + extensions: [AshStateMachine, AshJason.Resource, AshJsonApi.Resource] + + neo4j do + label :Rsp + end + + json_api do + type "rsp" + end + + jason do + pick [:id, :name, :short_name, :epid, :state] + compact true + end + + state_machine do + initial_states [:inactive] + default_initial_state :inactive + state_attribute :state + + transitions do + transition action: :activate, from: [:inactive, :suspended], to: :active + transition action: :suspend, from: :active, to: :suspended + transition action: :deactivate, from: [:active, :suspended], to: :inactive + end + end + + attributes do + attribute :id, :uuid do + primary_key? true + allow_nil? false + public? true + default &Ash.UUID.generate/0 + source :uuid + end + + attribute :name, :string do + description "the RSP's registered trading name" + allow_nil? false + public? true + end + + attribute :short_name, :atom do + description "atom identifier used as the actor for authorisation" + allow_nil? false + public? true + end + + attribute :epid, :string do + description "four-digit regulator-assigned provider identifier, in historical sequence" + allow_nil? false + public? true + constraints [match: ~r/^\d{4}$/] + end + + attribute :state, :atom do + allow_nil? false + default :inactive + public? true + constraints [one_of: [:active, :suspended, :inactive]] + end + + create_timestamp :created_at + update_timestamp :updated_at + end + + actions do + defaults [:destroy] + + read :read do + primary? true + end + + read :list do + prepare build(sort: [epid: :asc]) + end + + create :create do + accept [:name, :short_name, :epid] + upsert? true + upsert_identity :unique_epid + end + + update :activate do + require_atomic? false + change transition_state(:active) + end + + update :suspend do + require_atomic? false + change transition_state(:suspended) + end + + update :deactivate do + require_atomic? false + change transition_state(:inactive) + end + end + + identities do + identity :unique_epid, [:epid] + identity :unique_name, [:name] + identity :unique_short_name, [:short_name] + end + + policies do + bypass DiffoExample.Nbn.Checks.NoActor do + authorize_if always() + end + + bypass actor_attribute_equals(:role, :admin) do + authorize_if always() + end + + policy action_type(:read) do + authorize_if always() + end + end + + field_policies do + field_policy :state do + authorize_if DiffoExample.Nbn.Checks.NoActor + authorize_if actor_attribute_equals(:role, :admin) + authorize_if expr(^actor(:id) == id) + end + + field_policy :* do + authorize_if always() + end + end +end diff --git a/lib/nbn/resources/uni.ex b/lib/nbn/resources/uni.ex index 6910cb4..5ddf015 100644 --- a/lib/nbn/resources/uni.ex +++ b/lib/nbn/resources/uni.ex @@ -24,7 +24,8 @@ defmodule DiffoExample.Nbn.Uni do use Ash.Resource, fragments: [BaseInstance], domain: Nbn, - extensions: [AshJsonApi.Resource] + extensions: [AshJsonApi.Resource], + authorizers: [Ash.Policy.Authorizer] json_api do type "uni" @@ -128,4 +129,18 @@ defmodule DiffoExample.Nbn.Uni do uni: [port, technology] ) end + + policies do + bypass DiffoExample.Nbn.Checks.NoActor do + authorize_if always() + end + + bypass actor_attribute_equals(:role, :admin) do + authorize_if always() + end + + policy action_type(:read) do + authorize_if always() + end + end end diff --git a/lib/nbn/rsp_ownership.ex b/lib/nbn/rsp_ownership.ex new file mode 100644 index 0000000..dfcfe6d --- /dev/null +++ b/lib/nbn/rsp_ownership.ex @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.RspOwnership do + @moduledoc """ + Shared RSP ownership policies for NBN resources. + + Injects the standard three-tier policy into any RSP-owned resource: + - nil actor → bypass (internal Perentie calls) + - admin role → bypass + - create with RSP actor → allowed (rsp_id stamped by SetRspId) + - read/update/destroy → RSP can only access its own records + """ + + defmacro __using__(_opts) do + quote do + policies do + bypass DiffoExample.Nbn.Checks.NoActor do + authorize_if always() + end + + bypass actor_attribute_equals(:role, :admin) do + authorize_if always() + end + + policy action_type(:create) do + authorize_if always() + end + + policy action_type([:read, :update, :destroy]) do + authorize_if DiffoExample.Nbn.Checks.OwnedByActor + end + end + end + end +end diff --git a/mix.exs b/mix.exs index 614a1e5..31c6573 100644 --- a/mix.exs +++ b/mix.exs @@ -59,8 +59,14 @@ defmodule DiffoExample.MixProject do logo: "logos/diffo.jpg", extras: [ "README.md": [title: "Guide"], - "diffo_example.livemd": [title: "Livebook Tutorial"], + "documentation/domains/diffo_example_nbn.livemd": [title: "NBN Livebook"], + "documentation/domains/nbn.md": [title: "The NBN Domain"], "LICENSES/MIT.md": [title: "License"] + ], + groups_for_extras: [ + Domains: ~r"documentation/domains", + "How To": ~r"documentation/how_to", + DSLs: ~r"documentation/dsls" ] ] end @@ -84,6 +90,8 @@ defmodule DiffoExample.MixProject do {:ash_json_api, "~> 1.6"}, {:plug_cowboy, "~> 2.7"}, {:req, "~> 0.5", only: [:dev, :test]}, + {:picosat_elixir, "~> 0.2.0"}, + {:simple_sat, ">= 0.0.0"}, {:igniter, "~> 0.6", only: [:dev, :test]}, {:ex_doc, "~> 0.37", only: [:dev, :test], runtime: false} ] diff --git a/mix.lock b/mix.lock index 233d094..136c6c6 100644 --- a/mix.lock +++ b/mix.lock @@ -16,6 +16,7 @@ "diffo": {:hex, :diffo, "0.2.0", "ac07bb5ea92d765601fba3e61e8a5dac5c3c7f18b3a55bcf3019a574fda03d65", [:mix], [{:ash, ">= 3.24.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_jason, "~> 3.0", [hex: :ash_jason, repo: "hexpm", optional: false]}, {:ash_neo4j, "~> 0.3.1", [hex: :ash_neo4j, repo: "hexpm", optional: false]}, {:ash_outstanding, "~> 0.2.3", [hex: :ash_outstanding, repo: "hexpm", optional: false]}, {:ash_state_machine, "~> 0.2.12", [hex: :ash_state_machine, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm", "2a140d9e427e30b06b29a04eeafec8b98d7acfeaffdbfa06cf6c152998302503"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, + "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_doc": {:hex, :ex_doc, "0.40.1", "67542e4b6dde74811cfd580e2c0149b78010fd13001fda7cfeb2b2c2ffb1344d", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "bcef0e2d360d93ac19f01a85d58f91752d930c0a30e2681145feea6bd3516e00"}, "finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"}, @@ -39,6 +40,7 @@ "phoenix": {:hex, :phoenix, "1.8.5", "919db335247e6d4891764dc3063415b0d2457641c5f9b3751b5df03d8e20bbcf", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "83b2bb125127e02e9f475c8e3e92736325b5b01b0b9b05407bcb4083b7a32485"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, + "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, "plug_cowboy": {:hex, :plug_cowboy, "2.8.0", "07789e9c03539ee51bb14a07839cc95aa96999fd8846ebfd28c97f0b50c7b612", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9cbfaaf17463334ca31aed38ea7e08a68ee37cabc077b1e9be6d2fb68e0171d0"}, "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, @@ -46,6 +48,7 @@ "reactor": {:hex, :reactor, "1.0.1", "ca3b5cf3c04ec8441e67ea2625d0294939822060b1bfd00ffdaaf75b7682d991", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "3497db2b204c9a3cabdaf1b26d2405df1dfbb138ce0ce50e616e9db19fec0043"}, "req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"}, "rewrite": {:hex, :rewrite, "1.3.0", "67448ba7975690b35ba7e7f35717efcce317dbd5963cb0577aa7325c1923121a", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "d111ac7ff3a58a802ef4f193bbd1831e00a9c57b33276e5068e8390a212714a5"}, + "simple_sat": {:hex, :simple_sat, "0.1.4", "39baf72cdca14f93c0b6ce2b6418b72bbb67da98fa9ca4384e2f79bbc299899d", [:mix], [], "hexpm", "3569b68e346a5fd7154b8d14173ff8bcc829f2eb7b088c30c3f42a383443930b"}, "sourceror": {:hex, :sourceror, "1.12.0", "da354c5f35aad3cc1132f5d5b0d8437d865e2661c263260480bab51b5eedb437", [:mix], [], "hexpm", "755703683bd014ebcd5de9acc24b68fb874a660a568d1d63f8f98cd8a6ef9cd0"}, "spark": {:hex, :spark, "2.6.1", "b0100216d3883c6a281cb2434af45afbd808695aadb034923cbaf7d8a2ba46ab", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "77bbefa5263bb6b70e1195bc0fc662ddb8ef5937a356a77ae072e56983ad13f0"}, "spitfire": {:hex, :spitfire, "0.3.11", "79dfcb033762470de472c1c26ea2b4e3aca74700c685dbffd9a13466272c323d", [:mix], [], "hexpm", "eb6e2dadf63214e8bfe65ca9788cef2b03b01027365d78d3c0e3d9ebd3d5b7b4"}, diff --git a/test/nbn/rsp_test.exs b/test/nbn/rsp_test.exs new file mode 100644 index 0000000..e009225 --- /dev/null +++ b/test/nbn/rsp_test.exs @@ -0,0 +1,132 @@ +# SPDX-FileCopyrightText: 2025 diffo_example contributors +# +# SPDX-License-Identifier: MIT + +defmodule DiffoExample.Nbn.RspTest do + @moduledoc false + use ExUnit.Case + alias DiffoExample.Nbn + alias DiffoExample.Nbn.Rsp + + setup_all do + AshNeo4j.BoltyHelper.start() + end + + setup do + on_exit(fn -> + AshNeo4j.Neo4jHelper.delete_all() + end) + end + + defp create_rsp(attrs) do + {:ok, rsp} = Nbn.create_rsp(attrs) + {:ok, rsp} = Nbn.activate_rsp(rsp) + rsp + end + + describe "RSP resource" do + test "create and activate an RSP" do + {:ok, rsp} = Nbn.create_rsp(%{name: "Wedge-tail Telecom", short_name: :wedgetail, epid: "8001"}) + + assert is_struct(rsp, Rsp) + assert rsp.state == :inactive + assert rsp.epid == "8001" + assert rsp.short_name == :wedgetail + + {:ok, rsp} = Nbn.activate_rsp(rsp) + assert rsp.state == :active + end + + test "RSP state machine: activate → suspend → deactivate" do + {:ok, rsp} = Nbn.create_rsp(%{name: "Wedge-tail Telecom", short_name: :wedgetail, epid: "8001"}) + + {:ok, rsp} = Nbn.activate_rsp(rsp) + assert rsp.state == :active + + {:ok, rsp} = Nbn.suspend_rsp(rsp) + assert rsp.state == :suspended + + {:ok, rsp} = Nbn.deactivate_rsp(rsp) + assert rsp.state == :inactive + end + + test "epid must be exactly 4 digits" do + assert {:error, _} = Nbn.create_rsp(%{name: "Bad RSP", short_name: :bad, epid: "123"}) + assert {:error, _} = Nbn.create_rsp(%{name: "Bad RSP", short_name: :bad, epid: "12345"}) + assert {:error, _} = Nbn.create_rsp(%{name: "Bad RSP", short_name: :bad, epid: "abcd"}) + end + + test "get RSP by short_name" do + create_rsp(%{name: "Wedge-tail Telecom", short_name: :wedgetail, epid: "8001"}) + + {:ok, rsp} = Nbn.get_rsp_by_short_name(:wedgetail) + assert rsp.short_name == :wedgetail + assert rsp.epid == "8001" + end + + test "get RSP by epid" do + create_rsp(%{name: "Quokka Connect", short_name: :quokka, epid: "8002"}) + + {:ok, rsp} = Nbn.get_rsp_by_epid("8002") + assert rsp.short_name == :quokka + end + end + + describe "RSP multi-tenancy" do + setup do + wedgetail = create_rsp(%{name: "Wedge-tail Telecom", short_name: :wedgetail, epid: "8001"}) + quokka = create_rsp(%{name: "Quokka Connect", short_name: :quokka, epid: "8002"}) + %{wedgetail: wedgetail, quokka: quokka} + end + + test "build stamps rsp_id from actor", %{wedgetail: wedgetail} do + {:ok, resource} = Nbn.build_nbn_ethernet(%{}, actor: wedgetail) + assert resource.rsp_id == wedgetail.id + end + + test "build without actor leaves rsp_id nil" do + {:ok, resource} = Nbn.build_nbn_ethernet(%{}) + assert is_nil(resource.rsp_id) + end + + test "RSP can read its own resource", %{wedgetail: wedgetail} do + {:ok, resource} = Nbn.build_nbn_ethernet(%{}, actor: wedgetail) + assert {:ok, found} = Nbn.get_nbn_ethernet_by_id(resource.id, actor: wedgetail) + assert found.id == resource.id + end + + test "RSP cannot read another RSP's resource", %{wedgetail: wedgetail, quokka: quokka} do + {:ok, resource} = Nbn.build_nbn_ethernet(%{}, actor: wedgetail) + assert {:error, _} = Nbn.get_nbn_ethernet_by_id(resource.id, actor: quokka) + end + + test "RSP cannot update another RSP's resource", %{wedgetail: wedgetail, quokka: quokka} do + {:ok, resource} = Nbn.build_nbn_ethernet(%{}, actor: wedgetail) + + assert {:error, %Ash.Error.Forbidden{}} = + Nbn.define_nbn_ethernet(resource, %{characteristic_value_updates: []}, actor: quokka) + end + + test "nil actor (internal call) can read any RSP's resource", %{wedgetail: wedgetail} do + {:ok, resource} = Nbn.build_nbn_ethernet(%{}, actor: wedgetail) + assert {:ok, found} = Nbn.get_nbn_ethernet_by_id(resource.id) + assert found.id == resource.id + end + + test "admin actor can read any RSP's resource", %{wedgetail: wedgetail} do + admin = %{id: Ash.UUID.generate(), role: :admin} + {:ok, resource} = Nbn.build_nbn_ethernet(%{}, actor: wedgetail) + assert {:ok, found} = Nbn.get_nbn_ethernet_by_id(resource.id, actor: admin) + assert found.id == resource.id + end + + test "two RSPs own separate resources", %{wedgetail: wedgetail, quokka: quokka} do + {:ok, wt_resource} = Nbn.build_nbn_ethernet(%{}, actor: wedgetail) + {:ok, qq_resource} = Nbn.build_nbn_ethernet(%{}, actor: quokka) + + assert wt_resource.rsp_id == wedgetail.id + assert qq_resource.rsp_id == quokka.id + refute wt_resource.rsp_id == qq_resource.rsp_id + end + end +end From 439c5ebfc7181cb280322cd6f64d6004133d2c1c Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Sun, 26 Apr 2026 09:47:52 +0930 Subject: [PATCH 13/13] update CHANGELOG for v0.2.0 --- CHANGELOG.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b033778..16391cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,10 +31,16 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ### Fixes: * fixed relationship enrichment inconsistent across neo4j versions -## [v0.2.0](https://github.com/diffo-dev/diffo/compare/v0.0.4..v0.2.0) (2026-04-24) +## [v0.2.0](https://github.com/diffo-dev/diffo/compare/v0.0.4..v0.2.0) (2026-04-26) ### Maintenance: * updated to diffo 0.2.0 ### Features: -* new NBN domain modelling NBN Ethernet access and constituent resources (UNI, AVC, NTD, CVC, NNI Group, NNI), JSON API and livebook \ No newline at end of file +* new NBN domain modelling NBN Ethernet access and constituent resources (UNI, AVC, NTD, CVC, NNI Group, NNI) +* JSON API via AshJsonApi and Plug.Cowboy +* RSP resource with AshStateMachine lifecycle (inactive/active/suspended) and Ash Policy authorisation +* RSP multi-tenancy: SetRspId change, OwnedByActor and NoActor policy checks, RspOwnership macro shared across RSP-owned resources +* NTD and UNI modelled as NBN-owned infrastructure — readable by any RSP, mutable only by internal calls +* Interactive NBN livebook with Kino RSP selector and actor-scoped provisioning flow +* NBN domain documentation including Perentie ecosystem narrative \ No newline at end of file