-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #554 from podlove/add-consistence-checker
Add consistence checker
- Loading branch information
Showing
10 changed files
with
371 additions
and
176 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
defmodule Radiator.Outline.Validations do | ||
@moduledoc """ | ||
Collection of consistency validations for the outline tree. | ||
""" | ||
alias Radiator.Outline | ||
alias Radiator.Outline.Node | ||
alias Radiator.Outline.NodeRepository | ||
|
||
def validate_consistency_for_move( | ||
%{prev_id: new_prev_id, parent_id: new_parent_id}, | ||
new_prev_id, | ||
new_parent_id, | ||
_parent_node | ||
) do | ||
{:error, :noop} | ||
end | ||
|
||
# when prev is nil, every parent is allowed | ||
def validate_consistency_for_move( | ||
node, | ||
nil, | ||
_new_parent_id, | ||
_parent_node | ||
) do | ||
{:ok, node} | ||
end | ||
|
||
# when prev is not nil, parent and prev must be consistent | ||
def validate_consistency_for_move( | ||
node, | ||
new_prev_id, | ||
new_parent_id, | ||
_parent_node | ||
) do | ||
if NodeRepository.get_node(new_prev_id).parent_id == new_parent_id do | ||
{:ok, node} | ||
else | ||
{:error, :parent_and_prev_not_consistent} | ||
end | ||
end | ||
|
||
@doc """ | ||
Validates a tree for an episode. | ||
Returns :ok if the tree is valid | ||
""" | ||
def validate_tree_for_episode(episode_id) do | ||
{:ok, tree_nodes} = Outline.get_node_tree(episode_id) | ||
|
||
if Enum.count(tree_nodes) == NodeRepository.count_nodes_by_episode(episode_id) do | ||
validate_tree_nodes(tree_nodes) | ||
else | ||
{:error, :node_count_not_consistent} | ||
end | ||
end | ||
|
||
# iterate through the levels of the tree | ||
# every level has 1 node with prev_id nil | ||
# all other nodes in level have prev_id set and are connected to the previous node | ||
# should be used in dev and test only | ||
# might crash if the tree is not consistent | ||
defp validate_tree_nodes(tree_nodes) do | ||
tree_nodes | ||
|> Enum.group_by(& &1.parent_id) | ||
|> Enum.map(fn {_level, nodes} -> | ||
validate_sub_tree(nodes) | ||
end) | ||
|> Enum.reject(&(&1 == :ok)) | ||
|> first_error() | ||
end | ||
|
||
defp first_error([]), do: :ok | ||
defp first_error([err | _]), do: err | ||
|
||
defp validate_sub_tree(nodes) do | ||
# get the node with prev_id nil | ||
first_node = Enum.find(nodes, &(&1.prev_id == nil)) | ||
# get the rest of the nodes | ||
rest_nodes = Enum.reject(nodes, &(&1.prev_id == nil)) | ||
|
||
if Enum.count(rest_nodes) + 1 != Enum.count(nodes) do | ||
{:error, :prev_id_not_consistent} | ||
else | ||
validate_prev_node(first_node, rest_nodes) | ||
end | ||
end | ||
|
||
def validate_prev_node(node, rest_nodes, searched_nodes \\ []) | ||
|
||
def validate_prev_node( | ||
%Node{uuid: id}, | ||
[%Node{prev_id: id} = node | rest_nodes], | ||
searched_nodes | ||
) do | ||
validate_prev_node(node, rest_nodes ++ searched_nodes, []) | ||
end | ||
|
||
def validate_prev_node(%Node{}, [], []), do: :ok | ||
|
||
def validate_prev_node(%Node{} = prev_node, [node | rest_nodes], search_nodes) do | ||
validate_prev_node(prev_node, rest_nodes, search_nodes ++ [node]) | ||
end | ||
|
||
def validate_prev_node(%Node{}, [], _search_nodes), do: {:error, :prev_id_not_consistent} | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
defmodule Radiator.Outline.ValidationsTest do | ||
@moduledoc false | ||
use Radiator.DataCase | ||
|
||
alias Radiator.Outline.Node | ||
alias Radiator.Outline.NodeRepository | ||
alias Radiator.Outline.Validations | ||
|
||
import Ecto.Query, warn: false | ||
|
||
describe "validate_tree_for_episode/1" do | ||
setup :complex_node_fixture | ||
|
||
test "validates a tree", %{ | ||
node_1: %Node{episode_id: episode_id} | ||
} do | ||
assert :ok = Validations.validate_tree_for_episode(episode_id) | ||
end | ||
|
||
test "a level might have different subtrees", %{ | ||
node_1: %Node{episode_id: episode_id} = node_1 | ||
} do | ||
{:ok, %Node{} = _nested_node} = | ||
%{ | ||
episode_id: episode_id, | ||
parent_id: node_1.uuid, | ||
prev_id: nil, | ||
content: "child of node 1" | ||
} | ||
|> NodeRepository.create_node() | ||
|
||
assert :ok = Validations.validate_tree_for_episode(episode_id) | ||
end | ||
|
||
test "when two nodes share the same prev_id the tree is invalid", %{ | ||
node_2: %Node{episode_id: episode_id} = node_2 | ||
} do | ||
{:ok, %Node{} = _node_invalid} = | ||
%{ | ||
episode_id: episode_id, | ||
parent_id: node_2.parent_id, | ||
prev_id: node_2.prev_id | ||
} | ||
|> NodeRepository.create_node() | ||
|
||
assert {:error, :prev_id_not_consistent} = | ||
Validations.validate_tree_for_episode(episode_id) | ||
end | ||
|
||
test "when a nodes has a non connected prev_id the tree is invalid", %{ | ||
node_2: %Node{episode_id: episode_id} = node_2 | ||
} do | ||
{:ok, %Node{} = _node_invalid} = | ||
%{ | ||
episode_id: episode_id, | ||
parent_id: node_2.parent_id, | ||
prev_id: node_2.prev_id | ||
} | ||
|> NodeRepository.create_node() | ||
|
||
assert {:error, :prev_id_not_consistent} = | ||
Validations.validate_tree_for_episode(episode_id) | ||
end | ||
|
||
test "when a parent has two childs with prev_id nil the tree is invalid", %{ | ||
nested_node_1: %Node{episode_id: episode_id, parent_id: parent_id} | ||
} do | ||
{:ok, %Node{} = _node_invalid} = | ||
%{ | ||
episode_id: episode_id, | ||
parent_id: parent_id, | ||
prev_id: nil, | ||
content: "invalid node" | ||
} | ||
|> NodeRepository.create_node() | ||
|
||
assert {:error, :prev_id_not_consistent} = | ||
Validations.validate_tree_for_episode(episode_id) | ||
end | ||
|
||
test "a tree with a node where parent and prev are not consistent is invalid", %{ | ||
parent_node: %Node{episode_id: episode_id} = parent_node, | ||
nested_node_2: nested_node_2 | ||
} do | ||
{:ok, %Node{} = _node_invalid} = | ||
%{ | ||
episode_id: episode_id, | ||
parent_id: parent_node.uuid, | ||
prev_id: nested_node_2.uuid | ||
} | ||
|> NodeRepository.create_node() | ||
|
||
result = Validations.validate_tree_for_episode(episode_id) | ||
assert {:error, :prev_id_not_consistent} = result | ||
end | ||
end | ||
end |
Oops, something went wrong.