diff --git a/lib/journey.ex b/lib/journey.ex index ecbfac7..3eb490e 100644 --- a/lib/journey.ex +++ b/lib/journey.ex @@ -68,7 +68,7 @@ defmodule Journey do [ %Step{ spec: spec, - compensation: {compensation, nil}, + compensation: {compensation, nil, :not_called}, transaction: {transaction, call(transaction, journey, type)} } ] @@ -116,13 +116,15 @@ defmodule Journey do %Step{transaction: {_, result}} when is_ok(result) -> false _ -> true end) do - rollback(%__MODULE__{journey | state: :failed}) + rollback(journey) else _ -> journey end end defp rollback(%__MODULE__{steps: steps} = journey) do + journey = %__MODULE__{journey | state: :failed} + steps = steps |> Enum.reverse() @@ -133,11 +135,11 @@ defmodule Journey do end defp call_compensation( - %Step{compensation: {func, _}, transaction: {_, result}} = step, + %Step{compensation: {func, _, :not_called}, transaction: {_, result}} = step, journey ) when is_function(func) and is_ok(result) do - %Step{step | compensation: {func, call(func, journey, :sync)}} + %Step{step | compensation: {func, call(func, journey, :sync), :called}} end defp call_compensation(step, _journey), do: step diff --git a/test/journey_test.exs b/test/journey_test.exs index 7078b60..5bad476 100644 --- a/test/journey_test.exs +++ b/test/journey_test.exs @@ -1,6 +1,7 @@ defmodule JourneyTest do use ExUnit.Case + require Logger alias Journey.Step describe "with a new journey" do @@ -48,19 +49,19 @@ defmodule JourneyTest do assert [ %Step{ transaction: {_, :ok}, - compensation: {_, nil} + compensation: {_, nil, :not_called} }, %Step{ transaction: {_, :ok}, - compensation: {_, nil} + compensation: {_, nil, :not_called} }, %Step{ transaction: {_, :ok}, - compensation: {_, nil} + compensation: {_, nil, :not_called} }, %Step{ transaction: {_, :ok}, - compensation: {_, nil} + compensation: {_, nil, :not_called} } ] = steps end @@ -114,23 +115,27 @@ defmodule JourneyTest do test "run compensation if sync transaction fail", %{journey: journey} do result = journey - |> Journey.run_async({__MODULE__, :test_compensation}, [:ok]) - |> Journey.run({__MODULE__, :test_compensation}, [:error]) + |> Journey.run_async({__MODULE__, :test_compensation}, [:ok, self()]) + |> Journey.run({__MODULE__, :test_compensation}, [:error, self()]) # Step will not add because before step will failed - |> Journey.run_async({__MODULE__, :test_compensation}, [:ok]) + |> Journey.run_async({__MODULE__, :test_compensation}, [:ok, self()]) assert %Journey{result: :error, state: :failed, steps: steps} = result assert [ %Step{ transaction: {_, :ok}, - compensation: {_, :ok} + compensation: {_, :ok, :called} }, %Step{ transaction: {_, :error}, - compensation: {_, nil} + compensation: {_, nil, :not_called} } ] = steps + + assert_receive :transaction_called + assert_receive :compensation_called + refute_receive :compensation_called end test "run compensation if async transaction fail", %{journey: journey} do @@ -141,52 +146,57 @@ defmodule JourneyTest do result = journey - |> Journey.run({__MODULE__, :test_compensation}, [{:ok, :any}]) - |> Journey.run_async({__MODULE__, :test_compensation}, [:ok]) - |> Journey.run_async({__MODULE__, :test_compensation}, [{:ok, :any}]) - |> Journey.run({__MODULE__, :test_compensation}, [:ok]) - |> Journey.run_async({__MODULE__, :test_compensation}, [fn_sleep]) - |> Journey.run_async({__MODULE__, :test_compensation}, [{:error, :any}]) + |> Journey.run({__MODULE__, :test_compensation}, [{:ok, :any}, self()]) + |> Journey.run_async({__MODULE__, :test_compensation}, [:ok, self()]) + |> Journey.run_async({__MODULE__, :test_compensation}, [{:ok, :any}, self()]) + |> Journey.run({__MODULE__, :test_compensation}, [:ok, self()]) + |> Journey.run_async({__MODULE__, :test_compensation}, [fn_sleep, self()]) + |> Journey.run_async({__MODULE__, :test_compensation}, [{:error, :any}, self()]) # Step will not add because before step will failed - |> Journey.run({__MODULE__, :test_compensation}, [{:ok, :any}]) + |> Journey.run({__MODULE__, :test_compensation}, [{:ok, :any}, self()]) assert %Journey{result: {:error, :any}, state: :failed, steps: steps} = result assert [ %Step{ transaction: {_, {:ok, :any}}, - compensation: {_, :ok} + compensation: {_, :ok, :called} }, %Step{ transaction: {_, :ok}, - compensation: {_, :ok} + compensation: {_, :ok, :called} }, %Step{ transaction: {_, {:ok, :any}}, - compensation: {_, :ok} + compensation: {_, :ok, :called} }, %Step{ transaction: {_, :ok}, - compensation: {_, :ok} + compensation: {_, :ok, :called} }, %Step{ transaction: {_, :ok}, - compensation: {_, :ok} + compensation: {_, :ok, :called} }, %Step{ transaction: {_, {:error, :any}}, - compensation: {_, nil} + compensation: {_, nil, :not_called} } ] = steps + + # Only call once each transaction + for _ <- 1..6, do: assert_receive :transaction_called + for _ <- 1..5, do: assert_receive :compensation_called + refute_receive :compensation_called end test "run compensation if sync transaction raise a exception", %{journey: journey} do result = journey - |> Journey.run_async({__MODULE__, :test_compensation}, [:ok]) - |> Journey.run({__MODULE__, :test_compensation}, [fn -> raise "Any error" end]) + |> Journey.run_async({__MODULE__, :test_compensation}, [:ok, self()]) + |> Journey.run({__MODULE__, :test_compensation}, [fn -> raise "Any error" end, self()]) # Step will not add because before step will failed - |> Journey.run_async({__MODULE__, :test_compensation}, [:ok]) + |> Journey.run_async({__MODULE__, :test_compensation}, [:ok, self()]) error = {:error, %RuntimeError{message: "Any error"}} assert %Journey{result: ^error, state: :failed, steps: steps} = result @@ -194,22 +204,26 @@ defmodule JourneyTest do assert [ %Step{ transaction: {_, :ok}, - compensation: {_, :ok} + compensation: {_, :ok, :called} }, %Step{ transaction: {_, ^error}, - compensation: {_, nil} + compensation: {_, nil, :not_called} } ] = steps + + assert_receive :transaction_called + assert_receive :compensation_called + refute_receive :compensation_called end test "run compensation if async transaction raise a exception", %{journey: journey} do result = journey - |> Journey.run_async({__MODULE__, :test_compensation}, [:ok]) - |> Journey.run_async({__MODULE__, :test_compensation}, [fn -> raise "Any error" end]) + |> Journey.run_async({__MODULE__, :test_compensation}, [:ok, self()]) + |> Journey.run_async({__MODULE__, :test_compensation}, [fn -> raise "Any error" end, self()]) # Step will not add because before step will failed - |> Journey.run({__MODULE__, :test_compensation}, [:ok]) + |> Journey.run({__MODULE__, :test_compensation}, [:ok, self()]) error = {:error, %RuntimeError{message: "Any error"}} assert %Journey{result: ^error, state: :failed, steps: steps} = result @@ -217,50 +231,61 @@ defmodule JourneyTest do assert [ %Step{ transaction: {_, :ok}, - compensation: {_, :ok} + compensation: {_, :ok, :called} }, %Step{ transaction: {_, ^error}, - compensation: {_, nil} + compensation: {_, nil, :not_called} } ] = steps + + assert_receive :transaction_called + assert_receive :compensation_called + refute_receive :compensation_called end test "run compensation if await a async transaction ended with timeout", %{journey: journey} do result = journey - |> Journey.run_async({__MODULE__, :test_compensation}, [:ok]) - |> Journey.run_async({__MODULE__, :test_compensation}, [fn -> :timer.sleep(500) end], 50) + |> Journey.run_async({__MODULE__, :test_compensation}, [:ok, self()]) + |> Journey.run_async({__MODULE__, :test_compensation}, [fn -> :timer.sleep(500) end, self()], 50) # Step will not be added because previous step has failed due to timeout - |> Journey.run({__MODULE__, :test_compensation}, [:ok]) - - assert %Journey{result: error, state: :failed, steps: steps} = result - assert {:error, {:timeout, %Step{spec: {{__MODULE__, :test_compensation}, _, 50}}}} = error - - assert [ - %Step{ - transaction: {_, :ok}, - compensation: {_, :ok} - }, - %Step{ - transaction: {_, {:error, {:timeout, %Step{}}}}, - compensation: {_, nil} - } - ] = steps + |> Journey.run({__MODULE__, :test_compensation}, [:ok, self()]) + + assert %Journey{result: error, state: :failed, steps: steps} = result + assert {:error, {:timeout, %Step{spec: {{__MODULE__, :test_compensation}, _, 50}}}} = error + + assert [ + %Step{ + transaction: {_, :ok}, + compensation: {_, :ok, :called} + }, + %Step{ + transaction: {_, {:error, {:timeout, %Step{}}}}, + compensation: {_, nil, :not_called} + } + ] = steps + + assert_receive :transaction_called + assert_receive :compensation_called + refute_receive :compensation_called end end - def test_compensation(result) when is_function(result) do - { - fn -> result.() end, - fn -> :ok end - } + def test_compensation(result, pid) when not is_function(result) do + test_compensation(fn -> result end, pid) end - def test_compensation(result) do + def test_compensation(result, pid) do { - fn -> result end, - fn -> :ok end + fn -> + send(pid, :transaction_called) + result.() + end, + fn -> + send(pid, :compensation_called) + :ok + end } end