defmodule SplodeTest do use ExUnit.Case # Error classes defmodule HwError do @moduledoc false use Splode.ErrorClass, class: :hw end defmodule SwError do @moduledoc false use Splode.ErrorClass, class: :sw end defmodule ContainerErrorClass do @moduledoc false use Splode.ErrorClass, class: :ui end # Errors defmodule CpuError do @moduledoc false use Splode.Error, class: :hw def message(err), do: err |> inspect() end defmodule RamError do @moduledoc false use Splode.Error, class: :hw def message(err), do: err |> inspect() end defmodule DivByZeroException do @moduledoc false use Splode.Error, fields: [:num, :denom], class: :sw def message(err), do: err |> inspect() end defmodule NullReferenceException do @moduledoc false use Splode.Error, class: :sw def message(err), do: err |> inspect() end defmodule UnknownError do @moduledoc false use Splode.Error, fields: [:error], class: :unknown def message(err), do: err |> inspect() end defmodule ExampleContainerError do @moduledoc false use Splode.Error, fields: [:description], class: :ui def message(err), do: err |> inspect() end defmodule ContainerUnknownError do @moduledoc false use Splode.Error, fields: [:error], class: :unknown def message(err), do: err |> inspect() end defmodule SystemError do @moduledoc false use Splode, error_classes: [ hw: HwError, sw: SwError ], unknown_error: UnknownError end defmodule ContainerError do @moduledoc false use Splode, error_classes: [ interaction: ContainerErrorClass, hw: HwError, sw: SwError ], unknown_error: ContainerUnknownError, merge_with: [SystemError] end defmodule ContainerWithoutMergeWith do @moduledoc false use Splode, error_classes: [ interaction: ContainerErrorClass ], unknown_error: ContainerUnknownError, merge_with: [] end test "splode_error?" do refute SystemError.splode_error?(:error) refute SystemError.splode_error?(%{}) refute SystemError.splode_error?([]) assert SystemError.splode_error?(HwError.exception()) assert SystemError.splode_error?(SwError.exception()) assert SystemError.splode_error?(CpuError.exception()) assert SystemError.splode_error?(RamError.exception()) assert SystemError.splode_error?(DivByZeroException.exception()) assert SystemError.splode_error?(NullReferenceException.exception()) assert SystemError.splode_error?(UnknownError.exception()) end test "set_path" do null = NullReferenceException.exception(path: [:a]) null = SystemError.set_path(null, :b) assert null.path == [:b, :a] end describe "to_class" do setup do cpu = CpuError.exception() |> SystemError.to_error() ram = RamError.exception() |> SystemError.to_error() div = DivByZeroException.exception() |> SystemError.to_error() null = NullReferenceException.exception() |> SystemError.to_error() example_container_error = ExampleContainerError.exception() |> ContainerError.to_error() %{ cpu: cpu, ram: ram, div: div, null: null, example_container_error: example_container_error } end test "wraps errors in error class with same class", %{ cpu: cpu, ram: ram, div: div, null: null } do # H/W errors hw_error = [cpu, ram] |> SystemError.to_class() assert %HwError{errors: [^cpu, ^ram]} = hw_error # S/W errors sw_error = [div, null] |> SystemError.to_class() assert %SwError{errors: [^div, ^null]} = sw_error end test "error class with smaller index is selected for mixed class case", %{ cpu: cpu, ram: ram, div: div, null: null } do errors = [cpu, ram, div, null] |> Enum.shuffle() assert %HwError{errors: ^errors} = errors |> SystemError.to_class() end test "idempotent", %{ cpu: cpu, ram: ram, div: div, null: null } do error = [cpu, ram, div, null] |> Enum.shuffle() |> Enum.take(2) |> SystemError.to_class() assert error == error |> SystemError.to_class() end test "to_error flattens nested errors when included in merge_with", %{ cpu: cpu, ram: ram, example_container_error: example_container_error } do hw_error = [cpu, ram] |> SystemError.to_class() interaction_error = ContainerError.to_class([hw_error, example_container_error]) assert %{errors: [^cpu, ^ram, ^example_container_error]} = interaction_error end test "to_error doesn't flatten nested errors when not included in merge_with", %{ cpu: cpu, ram: ram, example_container_error: example_container_error } do hw_error = [cpu, ram] |> SystemError.to_class() interaction_error = ContainerWithoutMergeWith.to_class([hw_error, example_container_error]) assert %{errors: [%SplodeTest.ContainerUnknownError{}, %SplodeTest.ContainerUnknownError{}]} = interaction_error end end test "to_error" do error_tuple = {:error, :div_by_zero} assert %UnknownError{class: :unknown} = SystemError.to_error(error_tuple) runtime_error = %RuntimeError{} assert %UnknownError{class: :unknown} = SystemError.to_error(runtime_error) div_by_zero = DivByZeroException.exception() assert %DivByZeroException{} = SystemError.to_error(div_by_zero) end test "from_json" do div_by_zero = SystemError.from_json(DivByZeroException, %{"num" => 10, "denom" => 0}) assert %DivByZeroException{num: 10, denom: 0} = div_by_zero end end