Packages
commanded
0.15.0
1.4.10
1.4.9
1.4.8
1.4.7
1.4.6
1.4.3
1.4.2
1.4.1
1.4.0
1.4.0-rc.0
1.3.1
1.3.0
1.2.0
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-rc.1
1.0.0-rc.0
0.19.1
0.19.0
0.18.1
0.18.0
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.0
0.16.0-rc.1
0.16.0-rc.0
0.15.1
0.15.0
0.14.0
0.14.0-rc.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.5
0.8.4
0.8.3
0.8.1
0.8.0
0.7.1
0.6.2
0.6.1
0.6.0
0.4.0
0.3.1
0.3.0
0.2.1
0.2.0
0.1.0
Use Commanded to build your own Elixir applications following the CQRS/ES pattern.
Current section
Files
Jump to
Current section
Files
test/commands/routing_commands_test.exs
defmodule Commanded.Commands.RoutingCommandsTest do
use Commanded.StorageCase
alias Commanded.Commands.{ExecutionResult,UnregisteredCommand}
alias Commanded.EventStore
alias Commanded.ExampleDomain.BankAccount
alias Commanded.ExampleDomain.{OpenAccountHandler,DepositMoneyHandler,WithdrawMoneyHandler}
alias Commanded.ExampleDomain.BankAccount.Commands.{OpenAccount,CloseAccount,DepositMoney,WithdrawMoney}
alias Commanded.ExampleDomain.BankAccount.Events.BankAccountOpened
describe "routing to command handler" do
defmodule CommandHandlerRouter do
use Commanded.Commands.Router
dispatch OpenAccount, to: OpenAccountHandler, aggregate: BankAccount, identity: :account_number
dispatch DepositMoney, to: DepositMoneyHandler, aggregate: BankAccount, identity: :account_number
end
test "should dispatch command to registered handler" do
assert :ok = CommandHandlerRouter.dispatch(%OpenAccount{account_number: "ACC123", initial_balance: 1_000})
end
test "should fail to dispatch unregistered command" do
assert {:error, :unregistered_command} = CommandHandlerRouter.dispatch(%UnregisteredCommand{})
end
test "should fail to dispatch command with nil identity" do
assert {:error, :invalid_aggregate_identity} = CommandHandlerRouter.dispatch(%OpenAccount{account_number: nil, initial_balance: 1_000})
end
end
describe "routing to aggregate" do
alias Commanded.Commands.AggregateRouter
alias Commanded.Commands.AggregateRoot.Command
test "should dispatch command to registered handler" do
assert :ok = AggregateRouter.dispatch(%Command{uuid: UUID.uuid4})
end
test "should fail to dispatch unregistered command" do
assert {:error, :unregistered_command} = AggregateRouter.dispatch(%UnregisteredCommand{})
end
end
describe "identify aggregate" do
alias Commanded.Commands.IdentityAggregateRouter
alias Commanded.Commands.IdentityAggregate.IdentityCommand
test "should dispatch command to registered handler" do
assert :ok = IdentityAggregateRouter.dispatch(%IdentityCommand{uuid: UUID.uuid4})
end
test "should append events to stream using identity prefix" do
uuid = UUID.uuid4()
assert :ok = IdentityAggregateRouter.dispatch(%IdentityCommand{uuid: uuid})
recorded_events = EventStore.stream_forward("prefix-" <> uuid, 0) |> Enum.to_list()
assert length(recorded_events) == 1
end
end
describe "identify aggregate using function" do
alias Commanded.Commands.IdentityFunctionRouter
alias Commanded.Commands.IdentityFunctionAggregate.IdentityFunctionCommand
test "should dispatch command to registered handler" do
assert :ok = IdentityFunctionRouter.dispatch(%IdentityFunctionCommand{uuid: UUID.uuid4})
end
test "should append events to stream" do
uuid = UUID.uuid4()
assert :ok = IdentityFunctionRouter.dispatch(%IdentityFunctionCommand{uuid: uuid})
recorded_events = EventStore.stream_forward("fun-prefix-" <> uuid, 0) |> Enum.to_list()
assert length(recorded_events) == 1
end
end
test "should prevent duplicate registrations for a command" do
# compile time safety net to prevent duplicate command registrations
assert_raise RuntimeError, "duplicate command registration for: Commanded.ExampleDomain.BankAccount.Commands.OpenAccount", fn ->
Code.eval_string """
alias Commanded.ExampleDomain.BankAccount
alias Commanded.ExampleDomain.BankAccount.Commands.OpenAccount
defmodule Handler do
def handle(%BankAccount{}, %OpenAccount{}), do: []
end
defmodule DuplicateRouter do
use Commanded.Commands.Router
dispatch OpenAccount, to: Handler, aggregate: BankAccount, identity: :account_number
dispatch OpenAccount, to: Handler, aggregate: BankAccount, identity: :account_number
end
"""
end
end
test "should prevent registration for a command handler without a `handle/2` function" do
# compile time safety net to prevent duplicate command registrations
assert_raise RuntimeError, "command handler InvalidHandler does not define a function: handle/2", fn ->
Code.eval_string """
alias Commanded.ExampleDomain.BankAccount
alias Commanded.ExampleDomain.BankAccount.Commands.OpenAccount
defmodule InvalidHandler do
end
defmodule InvalidRouter do
use Commanded.Commands.Router
dispatch OpenAccount, to: InvalidHandler, aggregate: BankAccount, identity: :account_number
end
"""
end
end
test "should show a help note when bad argument given to a `dispatch/2` function" do
assert_raise RuntimeError, """
unexpected dispatch parameter "id"
available params are: to, function, aggregate, identity, identity_prefix, timeout, lifespan, consistency
""",
fn ->
Code.eval_string """
alias Commanded.ExampleDomain.BankAccount
alias Commanded.ExampleDomain.BankAccount.Commands.OpenAccount
defmodule InvalidRouter do
use Commanded.Commands.Router
dispatch OpenAccount, to: InvalidHandler, aggregate: BankAccount, id: :account_number
end
"""
end
end
test "should prevent registrations for a invalid command module" do
assert_raise RuntimeError, "module `UnknownCommand` does not exist, perhaps you forgot to `alias` the namespace", fn ->
Code.eval_string """
alias Commanded.ExampleDomain.BankAccount
alias Commanded.ExampleDomain.OpenAccountHandler
defmodule InvalidCommandRouter do
use Commanded.Commands.Router
dispatch UnknownCommand, to: OpenAccountHandler, aggregate: BankAccount, identity: :account_number
end
"""
end
end
test "should prevent registrations for an invalid command handler module" do
assert_raise RuntimeError, "module `UnknownHandler` does not exist, perhaps you forgot to `alias` the namespace", fn ->
Code.eval_string """
alias Commanded.ExampleDomain.BankAccount
alias Commanded.ExampleDomain.BankAccount.Commands.OpenAccount
defmodule InvalidHandlerRouter do
use Commanded.Commands.Router
dispatch OpenAccount, to: UnknownHandler, aggregate: BankAccount, identity: :account_number
end
"""
end
end
test "should prevent registrations for an invalid aggregate module" do
assert_raise RuntimeError, "module `UnknownAggregate` does not exist, perhaps you forgot to `alias` the namespace", fn ->
Code.eval_string """
alias Commanded.ExampleDomain.BankAccount.Commands.OpenAccount
alias Commanded.ExampleDomain.OpenAccountHandler
defmodule InvalidAggregateRouter do
use Commanded.Commands.Router
dispatch OpenAccount, to: OpenAccountHandler, aggregate: UnknownAggregate, identity: :account_number
end
"""
end
end
defmodule MultiCommandRouter do
use Commanded.Commands.Router
dispatch [OpenAccount,CloseAccount], to: OpenAccountHandler, aggregate: BankAccount, identity: :account_number
end
test "should allow multiple module registrations for multiple commands in a single dispatch" do
assert :ok == MultiCommandRouter.dispatch(%OpenAccount{account_number: "ACC123", initial_balance: 1_000})
assert :ok == MultiCommandRouter.dispatch(%CloseAccount{account_number: "ACC123"})
end
defmodule MultiCommandHandlerRouter do
use Commanded.Commands.Router
dispatch [OpenAccount,CloseAccount], to: OpenAccountHandler, aggregate: BankAccount, identity: :account_number
dispatch [DepositMoney], to: DepositMoneyHandler, aggregate: BankAccount, identity: :account_number
dispatch [WithdrawMoney], to: WithdrawMoneyHandler, aggregate: BankAccount, identity: :account_number
end
test "should allow multiple module registrations for different command handlers" do
assert :ok == MultiCommandHandlerRouter.dispatch(%OpenAccount{account_number: "ACC123", initial_balance: 1_000})
assert :ok == MultiCommandHandlerRouter.dispatch(%DepositMoney{account_number: "ACC123", amount: 100})
end
describe "include aggregate version" do
test "should return aggregate's updated stream version" do
assert {:ok, 1} == MultiCommandHandlerRouter.dispatch(%OpenAccount{account_number: "ACC123", initial_balance: 1_000}, include_aggregate_version: true)
assert {:ok, 2} == MultiCommandHandlerRouter.dispatch(%DepositMoney{account_number: "ACC123", amount: 100}, include_aggregate_version: true)
end
end
test "should allow setting metadata" do
metadata = %{"ip_address" => "127.0.0.1"}
assert :ok == MultiCommandHandlerRouter.dispatch(%OpenAccount{account_number: "ACC123", initial_balance: 1_000}, metadata: metadata)
assert :ok == MultiCommandHandlerRouter.dispatch(%DepositMoney{account_number: "ACC123", amount: 100}, metadata: metadata)
events = EventStore.stream_forward("ACC123") |> Enum.to_list()
assert length(events) == 2
Enum.each(events, fn event ->
assert event.metadata == metadata
end)
end
describe "include execution result" do
test "should return created events" do
metadata = %{"ip_address" => "127.0.0.1"}
command = %OpenAccount{account_number: "ACC123", initial_balance: 1_000}
assert MultiCommandHandlerRouter.dispatch(command, metadata: metadata, include_execution_result: true) ==
{
:ok,
%ExecutionResult{
aggregate_uuid: "ACC123",
aggregate_version: 1,
events: [%BankAccountOpened{account_number: "ACC123", initial_balance: 1000}],
metadata: metadata,
}
}
end
end
end