Skip to content

Commit 5c874c0

Browse files
committed
broadened validation
1 parent 1bd047e commit 5c874c0

2 files changed

Lines changed: 118 additions & 12 deletions

File tree

lib/diffo/provider/assigner/assigner.ex

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,26 @@ defmodule Diffo.Provider.Assigner do
1313
alias Diffo.Provider.AssignableCharacteristic
1414
alias Diffo.Provider.AssignmentRelationship
1515

16+
@assignable_resource_states [:installing, :operating]
17+
@assignable_service_states [:feasibilityChecked, :reserved, :inactive, :active, :suspended]
18+
19+
@doc """
20+
The resource lifecycle states from which an instance may make assignments.
21+
"""
22+
def assignable_resource_states, do: @assignable_resource_states
23+
24+
@doc """
25+
The service lifecycle states from which an instance may make assignments.
26+
"""
27+
def assignable_service_states, do: @assignable_service_states
28+
1629
@doc """
1730
Assign a thing using the pool declared via `pools do` on the instance module.
1831
The thing name is looked up from the pool declaration.
1932
"""
2033
def assign(result, changeset, pool_name)
2134
when is_struct(result) and is_struct(changeset, Ash.Changeset) and is_atom(pool_name) do
22-
with :ok <- check_lifecycle(result) do
35+
with :ok <- assignable_state?(result) do
2336
case result.__struct__.pool(pool_name) do
2437
nil -> {:error, "pool #{pool_name} not declared on #{result.__struct__}"}
2538
pool -> assign(result, changeset, pool_name, pool.thing)
@@ -61,17 +74,23 @@ defmodule Diffo.Provider.Assigner do
6174
end
6275
end
6376

64-
defp check_lifecycle(%{type: :resource, resource_state: state}) when state != :operating,
65-
do:
66-
{:error, "cannot assign: resource lifecycle state is #{inspect(state)}, must be :operating"}
67-
68-
defp check_lifecycle(%{type: :service, service_state: state})
69-
when state not in [:active, :inactive],
70-
do:
71-
{:error,
72-
"cannot assign: service state is #{inspect(state)}, must be :active or :inactive"}
73-
74-
defp check_lifecycle(_), do: :ok
77+
@doc """
78+
Returns `:ok` if the instance is in a lifecycle state that permits assignment,
79+
otherwise `{:error, reason}`.
80+
"""
81+
def assignable_state?(%{type: :resource, resource_state: state})
82+
when state not in @assignable_resource_states,
83+
do:
84+
{:error,
85+
"cannot assign: resource lifecycle state is #{inspect(state)}, must be one of #{inspect(@assignable_resource_states)}"}
86+
87+
def assignable_state?(%{type: :service, service_state: state})
88+
when state not in @assignable_service_states,
89+
do:
90+
{:error,
91+
"cannot assign: service state is #{inspect(state)}, must be one of #{inspect(@assignable_service_states)}"}
92+
93+
def assignable_state?(_), do: :ok
7594

7695
defp create_assignment(result, pool, thing, value, assignee_id, alias_name)
7796
when is_struct(result) and is_atom(pool) and is_atom(thing) and is_integer(value) and

test/provider/extension/assigner_test.exs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ defmodule Diffo.Provider.Extension.AssignerTest do
66
@moduledoc false
77
use ExUnit.Case, async: true
88
@moduletag :domain_extended
9+
alias Diffo.Provider.Assigner
910
alias Diffo.Provider.Specification
1011
alias Diffo.Provider.Characteristic
1112
alias Diffo.Provider.Assignment
@@ -19,6 +20,52 @@ defmodule Diffo.Provider.Extension.AssignerTest do
1920
on_exit(&AshNeo4j.Sandbox.rollback/0)
2021
end
2122

23+
# Issue #168 — broadened lifecycle policy. Service-side now covers the full
24+
# committed lifecycle (excludes :initial, :cancelled, :terminated); resource
25+
# side now allows :installing in addition to :operating.
26+
describe "assignable_state?/1 (#168)" do
27+
test "resource: :operating is permitted" do
28+
assert :ok = Assigner.assignable_state?(%{type: :resource, resource_state: :operating})
29+
end
30+
31+
test "resource: :installing is permitted" do
32+
assert :ok = Assigner.assignable_state?(%{type: :resource, resource_state: :installing})
33+
end
34+
35+
test "resource: :planning is rejected" do
36+
assert {:error, msg} =
37+
Assigner.assignable_state?(%{type: :resource, resource_state: :planning})
38+
39+
assert msg =~ ":planning"
40+
end
41+
42+
test "resource: :retiring is rejected" do
43+
assert {:error, _} =
44+
Assigner.assignable_state?(%{type: :resource, resource_state: :retiring})
45+
end
46+
47+
test "service: committed lifecycle states are permitted" do
48+
for state <- [:feasibilityChecked, :reserved, :inactive, :active, :suspended] do
49+
assert :ok = Assigner.assignable_state?(%{type: :service, service_state: state}),
50+
"expected service_state #{inspect(state)} to be assignable"
51+
end
52+
end
53+
54+
test "service: :initial is rejected" do
55+
assert {:error, msg} =
56+
Assigner.assignable_state?(%{type: :service, service_state: :initial})
57+
58+
assert msg =~ ":initial"
59+
end
60+
61+
test "service: terminal states are rejected" do
62+
for state <- [:cancelled, :terminated] do
63+
assert {:error, _} = Assigner.assignable_state?(%{type: :service, service_state: state}),
64+
"expected service_state #{inspect(state)} to be rejected"
65+
end
66+
end
67+
end
68+
2269
describe "build card" do
2370
@tag :card
2471
test "create a card" do
@@ -213,5 +260,45 @@ defmodule Diffo.Provider.Extension.AssignerTest do
213260
assert encoding ==
214261
~s({\"id\":\"#{card.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{card.id}",\"category\":\"Network Resource\",\"description\":\"A Card Resource Instance\",\"resourceSpecification\":{\"id\":\"cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"name\":\"card\",\"version\":\"v1.0.0\"},\"lifecycleState\":\"operating\"})
215262
end
263+
264+
test "auto assign port to resource in :installing state (#168)" do
265+
{:ok, assignee} = Parties.build_shelf_with_installer()
266+
267+
{:ok, card} = Servo.build_card(%{})
268+
269+
updates = [
270+
card: [family: :ISAM, model: "EBLT48", technology: :adsl2Plus],
271+
ports: [first: 1, last: 48, assignable_type: "ADSL2+"]
272+
]
273+
274+
{:ok, card} = Servo.define_card(card, %{characteristic_value_updates: updates})
275+
{:ok, card} = Servo.lifecycle_card(card, %{resource_state: :installing})
276+
277+
{:ok, card} =
278+
Servo.assign_port(card, %{
279+
assignment: %Assignment{assignee_id: assignee.id, operation: :auto_assign}
280+
})
281+
282+
assert length(card.assignments) == 1
283+
end
284+
285+
test "assign rejected while resource is in :planning state (#168)" do
286+
{:ok, assignee} = Parties.build_shelf_with_installer()
287+
288+
{:ok, card} = Servo.build_card(%{})
289+
290+
updates = [
291+
card: [family: :ISAM, model: "EBLT48", technology: :adsl2Plus],
292+
ports: [first: 1, last: 48, assignable_type: "ADSL2+"]
293+
]
294+
295+
{:ok, card} = Servo.define_card(card, %{characteristic_value_updates: updates})
296+
{:ok, card} = Servo.lifecycle_card(card, %{resource_state: :planning})
297+
298+
assert {:error, _} =
299+
Servo.assign_port(card, %{
300+
assignment: %Assignment{assignee_id: assignee.id, operation: :auto_assign}
301+
})
302+
end
216303
end
217304
end

0 commit comments

Comments
 (0)