Add API to calculate a block reward for a block (#223)

pull/235/head
Alex Garibay 7 years ago committed by GitHub
parent 2c0ea545c3
commit 5bdea742f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 59
      apps/explorer/lib/explorer/chain.ex
  2. 188
      apps/explorer/lib/explorer/chain/block/range.ex
  3. 27
      apps/explorer/lib/explorer/chain/block/reward.ex
  4. 21
      apps/explorer/lib/explorer/chain/wei.ex
  5. 7
      apps/explorer/priv/repo/migrations/20180522154252_create_btree_gist_extension.exs
  6. 12
      apps/explorer/priv/repo/migrations/20180522154253_create_block_rewards.exs
  7. 110
      apps/explorer/test/explorer/chain/block/range_test.exs
  8. 41
      apps/explorer/test/explorer/chain_test.exs
  9. 35
      apps/explorer/test/support/factory.ex
  10. 32
      apps/explorer_web/lib/explorer_web/controllers/api/rpc/block_controller.ex
  11. 68
      apps/explorer_web/lib/explorer_web/controllers/api/rpc/rpc_translator.ex
  12. 14
      apps/explorer_web/lib/explorer_web/router.ex
  13. 28
      apps/explorer_web/lib/explorer_web/views/api/rpc/block_view.ex
  14. 19
      apps/explorer_web/lib/explorer_web/views/api/rpc/rpc_view.ex
  15. 80
      apps/explorer_web/test/explorer_web/controllers/api/rpc/block_controller_test.exs
  16. 94
      apps/explorer_web/test/explorer_web/controllers/api/rpc/rpc_translator_test.exs

@ -29,6 +29,7 @@ defmodule Explorer.Chain do
Wei
}
alias Explorer.Chain.Block.Reward
alias Explorer.Repo
@typedoc """
@ -228,6 +229,64 @@ defmodule Explorer.Chain do
Repo.aggregate(Block, :count, :hash)
end
@doc !"""
Returns a default value if no value is found.
"""
defmacrop default_if_empty(value, default) do
quote do
fragment("coalesce(?, ?)", unquote(value), unquote(default))
end
end
@doc !"""
Sum of the products of two columns.
"""
defmacrop sum_of_products(col_a, col_b) do
quote do
sum(fragment("?*?", unquote(col_a), unquote(col_b)))
end
end
@doc """
Reward for mining a block.
The block reward is the sum of the following:
* Sum of the transaction fees (gas_used * gas_price) for the block
* A static reward for miner (this value may change during the life of the chain)
* The reward for uncle blocks (1/32 * static_reward * number_of_uncles)
*NOTE*
Uncles are not currently accounted for.
"""
@spec block_reward(Block.t()) :: Wei.t()
def block_reward(%Block{number: block_number}) do
query =
from(
block in Block,
left_join: transaction in assoc(block, :transactions),
left_join: receipt in assoc(transaction, :receipt),
inner_join: block_reward in Reward,
on: fragment("? <@ ?", block.number, block_reward.block_range),
where: block.number == ^block_number,
group_by: block_reward.reward,
select: %{
transaction_reward: %Wei{
value: default_if_empty(sum_of_products(receipt.gas_used, transaction.gas_price), 0)
},
static_reward: block_reward.reward
}
)
%{
transaction_reward: transaction_reward,
static_reward: static_reward
} = Repo.one(query)
Wei.sum(transaction_reward, static_reward)
end
@doc """
Finds all `t:Explorer.Chain.Transaction.t/0` in the `t:Explorer.Chain.Block.t/0`.

@ -0,0 +1,188 @@
defmodule Explorer.Chain.Block.Range do
@moduledoc """
Represents a range of block numbers.
"""
alias Explorer.Chain.Block.Range
alias Postgrex.Range, as: PGRange
defstruct [:from, :to]
@typedoc """
A block number range where range boundaries are inclusive.
* `:from` - Lower inclusive bound of range.
* `:to` - Upper inclusive bound of range.
"""
@type t :: %Range{
from: integer() | :negative_infinity,
to: integer() | :infinity
}
@behaviour Ecto.Type
@doc """
The underlying Postgres type, `int8range`.
"""
@impl Ecto.Type
def type, do: :int8range
@doc """
Converts a value to a `Range`.
## Examples
Tuples of integers
iex> cast({1, 5})
{:ok, %Range{from: 1, to: 5}}
iex> cast({nil, 5})
{:ok, %Range{from: :negative_infinity, to: 5}}
iex> cast({1, nil})
{:ok, %Range{from: 1, to: :infinity}}
Postgres range strings
iex> cast("[1,5]")
{:ok, %Range{from: 1, to: 5}}
iex> cast("(0,6)")
{:ok, %Range{from: 1, to: 5}}
iex> cast("(,5]")
{:ok, %Range{from: :negative_infinity, to: 5}}
iex> cast("[1,)")
{:ok, %Range{from: 1, to: :infinity}}
Range
iex> cast(%Range{from: 1, to: 5})
{:ok, %Range{from: 1, to: 5}}
"""
@impl Ecto.Type
def cast({nil, upper}) when is_integer(upper) do
{:ok, %Range{from: :negative_infinity, to: upper}}
end
def cast({lower, nil}) when is_integer(lower) do
{:ok, %Range{from: lower, to: :infinity}}
end
def cast({lower, upper}) when is_integer(lower) and is_integer(upper) and lower <= upper do
{:ok, %Range{from: lower, to: upper}}
end
def cast(range_string) when is_binary(range_string) do
# Lower boundary should be either `[` or `(`
lower_boundary_values = "[\\[\\(]"
# Integer may or may not be present
integer = "\\d*"
# Upper boundary should be either `]` or `)`
upper_boundary_values = "[\\]\\)]"
range_regex = ~r"(#{lower_boundary_values})(#{integer}),(#{integer})(#{upper_boundary_values})"
case Regex.run(range_regex, range_string, capture: :all_but_first) do
[lower_boundary, lower, upper, upper_boundary] ->
block_range = %Range{
from: cast_lower(lower, lower_boundary),
to: cast_upper(upper, upper_boundary)
}
{:ok, block_range}
_ ->
:error
end
end
def cast(%Range{} = block_range), do: {:ok, block_range}
def cast(_), do: :error
defp cast_lower("", _symbol), do: :negative_infinity
defp cast_lower(boundary_value, "["), do: String.to_integer(boundary_value)
defp cast_lower(boundary_value, "("), do: String.to_integer(boundary_value) + 1
defp cast_upper("", _symbol), do: :infinity
defp cast_upper(boundary_value, "]"), do: String.to_integer(boundary_value)
defp cast_upper(boundary_value, ")"), do: String.to_integer(boundary_value) - 1
@doc """
Loads a range from the database and converts it to a `t:Range.t.0`.
## Example
iex> pg_range = %Postgrex.Range{
...> lower: 1,
...> lower_inclusive: true,
...> upper: 5,
...> upper_inclusive: true
...> }
iex> load(pg_range)
{:ok, %Range{from: 1, to: 5}}
"""
@impl Ecto.Type
def load(%PGRange{} = range) do
block_range = %Range{
from: parse_lower(range),
to: parse_upper(range)
}
{:ok, block_range}
end
def load(_), do: :error
defp parse_upper(%PGRange{upper: nil}), do: :infinity
defp parse_upper(%PGRange{upper: upper, upper_inclusive: true}), do: upper
defp parse_upper(%PGRange{upper: upper, upper_inclusive: false}), do: upper - 1
defp parse_lower(%PGRange{lower: nil}), do: :negative_infinity
defp parse_lower(%PGRange{lower: lower, lower_inclusive: true}), do: lower
defp parse_lower(%PGRange{lower: lower, lower_inclusive: false}), do: lower + 1
@doc """
Converts a `t:Range.t/0` to a persistable data value.
## Example
iex> dump(%Range{from: 1, to: 5})
{:ok,
%Postgrex.Range{
lower: 1,
lower_inclusive: true,
upper: 5,
upper_inclusive: true
}}
"""
@impl Ecto.Type
def dump(%Range{from: from, to: to}) do
upper = build_upper(to)
lower = build_lower(from)
range_params = Map.merge(lower, upper)
{:ok, struct!(PGRange, range_params)}
end
def dump(_), do: :error
defp build_lower(:negative_infinity) do
%{lower: nil, lower_inclusive: false}
end
defp build_lower(lower) do
%{lower: lower, lower_inclusive: true}
end
defp build_upper(:infinity) do
%{upper: nil, upper_inclusive: false}
end
defp build_upper(upper) do
%{upper: upper, upper_inclusive: true}
end
end

@ -0,0 +1,27 @@
defmodule Explorer.Chain.Block.Reward do
@moduledoc """
Represents the static reward given to the miner of a block in a range of block numbers.
"""
use Ecto.Schema
alias Explorer.Chain.Block.{Range, Reward}
alias Explorer.Chain.Wei
@typedoc """
The static reward given to the miner of a block.
* `:block_range` - Range of block numbers
* `:reward` - Reward given in Wei
"""
@type t :: %Reward{
block_range: Range.t(),
reward: Wei.t()
}
@primary_key false
schema "block_rewards" do
field(:block_range, Range)
field(:reward, Wei)
end
end

@ -20,6 +20,8 @@ defmodule Explorer.Chain.Wei do
"""
alias Explorer.Chain.Wei
defstruct ~w(value)a
@behaviour Ecto.Type
@ -111,6 +113,23 @@ defmodule Explorer.Chain.Wei do
@wei_per_ether Decimal.new(1_000_000_000_000_000_000)
@wei_per_gwei Decimal.new(1_000_000_000)
@doc """
Sums two Wei values.
## Example
iex> first = %Explorer.Chain.Wei{value: Decimal.new(123)}
iex> second = %Explorer.Chain.Wei{value: Decimal.new(1_000)}
iex> Explorer.Chain.Wei.sum(first, second)
%Explorer.Chain.Wei{value: Decimal.new(1_123)}
"""
@spec sum(Wei.t(), Wei.t()) :: Wei.t()
def sum(%Wei{value: wei_1}, %Wei{value: wei_2}) do
wei_1
|> Decimal.add(wei_2)
|> from(:wei)
end
@doc """
Converts `Decimal` representations of various wei denominations (wei, Gwei, ether) to
a wei base unit.
@ -185,7 +204,7 @@ defmodule Explorer.Chain.Wei do
Decimal.div(wei, @wei_per_gwei)
end
@spec to(t(), :wei) :: t()
@spec to(t(), :wei) :: wei()
def to(%__MODULE__{value: wei}, :wei), do: wei
end

@ -0,0 +1,7 @@
defmodule Explorer.Repo.Migrations.CreateBtreeGistExtension do
use Ecto.Migration
def change do
execute("CREATE EXTENSION IF NOT EXISTS btree_gist")
end
end

@ -0,0 +1,12 @@
defmodule Explorer.Repo.Migrations.CreateBlockRewards do
use Ecto.Migration
def change do
create table(:block_rewards, primary_key: false) do
add(:block_range, :int8range)
add(:reward, :decimal)
end
create(constraint(:block_rewards, :no_overlapping_ranges, exclude: ~s|gist (block_range WITH &&)|))
end
end

@ -0,0 +1,110 @@
defmodule Explorer.Chain.Block.RangeTest do
use ExUnit.Case
alias Explorer.Chain.Block.Range
alias Postgrex.Range, as: PGRange
doctest Explorer.Chain.Block.Range, import: true
describe "cast/1" do
test "with negative infinity lower bound and integer" do
assert Range.cast({nil, 2}) == {:ok, %Range{from: :negative_infinity, to: 2}}
end
test "with integer and infinity upper bound" do
assert Range.cast({2, nil}) == {:ok, %Range{from: 2, to: :infinity}}
end
test "with two integers" do
assert Range.cast({2, 10}) == {:ok, %Range{from: 2, to: 10}}
end
test "with a string" do
assert Range.cast("[2,10]") == {:ok, %Range{from: 2, to: 10}}
assert Range.cast("(2,10)") == {:ok, %Range{from: 3, to: 9}}
assert Range.cast("[2,)") == {:ok, %Range{from: 2, to: :infinity}}
assert Range.cast("(,10]") == {:ok, %Range{from: :negative_infinity, to: 10}}
assert Range.cast("{2,10}") == :error
end
test "with a block range" do
range = %Range{from: 2, to: 10}
assert Range.cast(range) == {:ok, range}
end
test "with an invalid input" do
assert Range.cast(2..10) == :error
end
end
describe "load/1" do
test "with inclusive finite bounds on Range" do
range = %PGRange{
lower: 2,
lower_inclusive: true,
upper: 10,
upper_inclusive: true
}
assert Range.load(range) == {:ok, %Range{from: 2, to: 10}}
end
test "with non-inclusive finite bounds on Range" do
range = %PGRange{
lower: 2,
lower_inclusive: false,
upper: 10,
upper_inclusive: false
}
assert Range.load(range) == {:ok, %Range{from: 3, to: 9}}
end
test "with infinite bounds" do
range = %PGRange{
lower: nil,
lower_inclusive: false,
upper: nil,
upper_inclusive: false
}
assert Range.load(range) == {:ok, %Range{from: :negative_infinity, to: :infinity}}
end
test "with an invalid input" do
assert Range.load("invalid") == :error
end
end
describe "dump/1" do
test "with infinite bounds" do
expected = %PGRange{
lower: nil,
lower_inclusive: false,
upper: nil,
upper_inclusive: false
}
assert Range.dump(%Range{from: :negative_infinity, to: :infinity}) == {:ok, expected}
end
test "with fininte bounds" do
expected = %PGRange{
lower: 2,
lower_inclusive: true,
upper: 10,
upper_inclusive: true
}
assert Range.dump(%Range{from: 2, to: 10}) == {:ok, expected}
end
test "with an invalid input" do
assert Range.dump("invalid") == :error
end
end
test "type/0" do
assert Range.type() == :int8range
end
end

@ -860,4 +860,45 @@ defmodule Explorer.ChainTest do
assert response == {:ok, address}
end
end
describe "block_reward/1" do
setup do
%{block_range: range} = block_reward = insert(:block_reward)
block = insert(:block, number: Enum.random(Range.new(range.from, range.to)))
insert(:transaction)
{:ok, block: block, block_reward: block_reward}
end
test "with block containing transactions", %{block: block, block_reward: block_reward} do
insert(
:transaction,
block: block,
index: 0,
gas_price: 1,
receipt: build(:receipt, gas_used: 1, transaction_index: 0)
)
insert(
:transaction,
block: block,
index: 1,
gas_price: 1,
receipt: build(:receipt, gas_used: 2, transaction_index: 1)
)
expected =
block_reward.reward
|> Wei.to(:wei)
|> Decimal.add(Decimal.new(3))
|> Wei.from(:wei)
assert expected == Chain.block_reward(block)
end
test "with block without transactions", %{block: block, block_reward: block_reward} do
assert block_reward.reward == Chain.block_reward(block)
end
end
end

@ -1,7 +1,19 @@
defmodule Explorer.Factory do
use ExMachina.Ecto, repo: Explorer.Repo
alias Explorer.Chain.{Address, Block, Data, Hash, InternalTransaction, Log, Receipt, Transaction}
alias Explorer.Chain.Block.{Range, Reward}
alias Explorer.Chain.{
Address,
Block,
Data,
Hash,
InternalTransaction,
Log,
Receipt,
Transaction
}
alias Explorer.Market.MarketHistory
alias Explorer.Repo
@ -103,6 +115,27 @@ defmodule Explorer.Factory do
}
end
def block_reward_factory do
# Generate ranges like 1 - 10,000; 10,001 - 20,000, 20,001 - 30,000; etc
x = sequence("block_range", & &1)
lower = x * 10_000 + 1
upper = lower + 9_999
wei_per_ether = Decimal.new(1_000_000_000_000_000_000)
reward_multiplier =
1..5
|> Enum.random()
|> Decimal.new()
reward = Decimal.mult(reward_multiplier, wei_per_ether)
%Reward{
block_range: %Range{from: lower, to: upper},
reward: reward
}
end
def receipt_factory do
%Receipt{
cumulative_gas_used: Enum.random(21_000..100_000),

@ -0,0 +1,32 @@
defmodule ExplorerWeb.API.RPC.BlockController do
use ExplorerWeb, :controller
alias Explorer.Chain
alias ExplorerWeb.Chain, as: ChainWeb
def getblockreward(conn, params) do
with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")},
{:ok, block_number} <- ChainWeb.param_to_block_number(unsafe_block_number),
block_options = [necessity_by_association: %{transactions: :optional}],
{:ok, block} <- Chain.number_to_block(block_number, block_options) do
reward = Chain.block_reward(block)
render(conn, :block_reward, block: block, reward: reward)
else
{:block_param, :error} ->
conn
|> put_status(400)
|> render(:error, error: "Query parameter 'blockno' is required")
{:error, :invalid} ->
conn
|> put_status(400)
|> render(:error, error: "Invalid block number")
{:error, :not_found} ->
conn
|> put_status(404)
|> render(:error, error: "Block does not exist")
end
end
end

@ -0,0 +1,68 @@
defmodule ExplorerWeb.API.RPC.RPCTranslator do
@moduledoc """
Converts an RPC-style request into a controller action.
Requests are expected to have URL query params like `?module=module&action=action`.
## Configuration
The plugs needs a map relating a `module` string to a controller module.
# In a router
forward "/api", RPCTranslator, %{"block" => BlockController}
"""
import Plug.Conn
alias ExplorerWeb.API.RPC.RPCView
alias Plug.Conn
alias Phoenix.Controller
def init(opts), do: opts
def call(%Conn{params: %{"module" => module, "action" => action}} = conn, translations) do
with {:ok, controller} <- translate_module(translations, module),
{:ok, action} <- translate_action(action),
{:ok, conn} <- call_controller(conn, controller, action) do
conn
else
_ ->
conn
|> put_status(400)
|> Controller.render(RPCView, :error, error: "Unknown action")
|> halt()
end
end
def call(%Conn{} = conn, _) do
conn
|> put_status(400)
|> Controller.render(RPCView, :error, error: "Params 'module' and 'action' are required parameters")
|> halt()
end
@doc false
@spec translate_module(map(), String.t()) :: {:ok, module()} | :error
def translate_module(translations, module) do
module_lowercase = String.downcase(module)
Map.fetch(translations, module_lowercase)
end
@doc false
@spec translate_action(String.t()) :: {:ok, atom()} | :error
def translate_action(action) do
action_lowercase = String.downcase(action)
{:ok, String.to_existing_atom(action_lowercase)}
rescue
ArgumentError -> :error
end
@doc false
@spec call_controller(Conn.t(), module(), atom()) :: {:ok, Conn.t()} | :error
def call_controller(conn, controller, action) do
{:ok, controller.call(conn, action)}
rescue
Conn.WrapperError -> :error
end
end

@ -18,10 +18,24 @@ defmodule ExplorerWeb.Router do
})
end
pipeline :api do
plug(:accepts, ["json"])
end
pipeline :set_locale do
plug(SetLocale, gettext: ExplorerWeb.Gettext, default_locale: "en")
end
scope "/api", ExplorerWeb.API.RPC do
pipe_through(:api)
alias ExplorerWeb.API.RPC
forward("/", RPCTranslator, %{
"block" => RPC.BlockController
})
end
scope "/", ExplorerWeb do
pipe_through(:browser)
pipe_through(:set_locale)

@ -0,0 +1,28 @@
defmodule ExplorerWeb.API.RPC.BlockView do
use ExplorerWeb, :view
alias Explorer.Chain.{Hash, Wei}
alias ExplorerWeb.API.RPC.RPCView
def render("block_reward.json", %{block: block, reward: reward}) do
reward_as_string =
reward
|> Wei.to(:wei)
|> Decimal.to_string(:normal)
data = %{
"blockNumber" => to_string(block.number),
"timeStamp" => DateTime.to_unix(block.timestamp),
"blockMiner" => Hash.to_string(block.miner_hash),
"blockReward" => reward_as_string,
"uncles" => nil,
"uncleInclusionReward" => nil
}
RPCView.render("show.json", data: data)
end
def render("error.json", %{error: error}) do
RPCView.render("error.json", error: error)
end
end

@ -0,0 +1,19 @@
defmodule ExplorerWeb.API.RPC.RPCView do
use ExplorerWeb, :view
def render("show.json", %{data: data}) do
%{
"status" => "1",
"message" => "OK",
"result" => data
}
end
def render("error.json", %{error: message}) do
%{
"status" => "0",
"message" => message,
"result" => nil
}
end
end

@ -0,0 +1,80 @@
defmodule ExplorerWeb.API.RPC.BlockControllerTest do
use ExplorerWeb.ConnCase
alias Explorer.Chain.{Hash, Wei}
describe "getblockreward" do
test "with missing block number", %{conn: conn} do
assert response =
conn
|> get("/api", %{"module" => "block", "action" => "getblockreward"})
|> json_response(400)
assert response["message"] =~ "'blockno' is required"
assert response["status"] == "0"
assert Map.has_key?(response, "result")
refute response["result"]
end
test "with an invalid block number", %{conn: conn} do
assert response =
conn
|> get("/api", %{"module" => "block", "action" => "getblockreward", "blockno" => "badnumber"})
|> json_response(400)
assert response["message"] =~ "Invalid block number"
assert response["status"] == "0"
assert Map.has_key?(response, "result")
refute response["result"]
end
test "with a block that doesn't exist", %{conn: conn} do
assert response =
conn
|> get("/api", %{"module" => "block", "action" => "getblockreward", "blockno" => "42"})
|> json_response(404)
assert response["message"] =~ "Block does not exist"
assert response["status"] == "0"
assert Map.has_key?(response, "result")
refute response["result"]
end
test "with a valid block", %{conn: conn} do
%{block_range: range} = block_reward = insert(:block_reward)
block = insert(:block, number: Enum.random(Range.new(range.from, range.to)))
insert(
:transaction,
block: block,
index: 0,
gas_price: 1,
receipt: build(:receipt, gas_used: 1, transaction_index: 0)
)
expected_reward =
block_reward.reward
|> Wei.to(:wei)
|> Decimal.add(Decimal.new(1))
|> Decimal.to_string(:normal)
expected_result = %{
"blockNumber" => "#{block.number}",
"timeStamp" => DateTime.to_unix(block.timestamp),
"blockMiner" => Hash.to_string(block.miner_hash),
"blockReward" => expected_reward,
"uncles" => nil,
"uncleInclusionReward" => nil
}
assert response =
conn
|> get("/api", %{"module" => "block", "action" => "getblockreward", "blockno" => "#{block.number}"})
|> json_response(200)
assert response["result"] == expected_result
assert response["status"] == "1"
assert response["message"] == "OK"
end
end
end

@ -0,0 +1,94 @@
defmodule ExplorerWeb.API.RPC.RPCTranslatorTest do
use ExplorerWeb.ConnCase
alias ExplorerWeb.API.RPC.RPCTranslator
alias Plug.Conn
defmodule TestController do
use ExplorerWeb, :controller
def test_action(conn, _) do
json(conn, %{})
end
end
setup %{conn: conn} do
conn = Phoenix.Controller.accepts(conn, ["json"])
{:ok, conn: conn}
end
test "init/1" do
assert RPCTranslator.init([]) == []
end
describe "call" do
test "with a bad module", %{conn: conn} do
conn = %Conn{conn | params: %{"module" => "test", "action" => "test"}}
result = RPCTranslator.call(conn, %{})
assert result.halted
assert response = json_response(result, 400)
assert response["message"] =~ "Unknown action"
assert response["status"] == "0"
assert Map.has_key?(response, "result")
refute response["result"]
end
test "with a bad action atom", %{conn: conn} do
conn = %Conn{conn | params: %{"module" => "test", "action" => "some_atom_that_should_not_exist"}}
result = RPCTranslator.call(conn, %{"test" => TestController})
assert result.halted
assert response = json_response(result, 400)
assert response["message"] =~ "Unknown action"
assert response["status"] == "0"
assert Map.has_key?(response, "result")
refute response["result"]
end
test "with an invalid controller action", %{conn: conn} do
conn = %Conn{conn | params: %{"module" => "test", "action" => "index"}}
result = RPCTranslator.call(conn, %{"test" => TestController})
assert result.halted
assert response = json_response(result, 400)
assert response["message"] =~ "Unknown action"
assert response["status"] == "0"
assert Map.has_key?(response, "result")
refute response["result"]
end
test "with missing params", %{conn: conn} do
result = RPCTranslator.call(conn, %{"test" => TestController})
assert result.halted
assert response = json_response(result, 400)
assert response["message"] =~ "'module' and 'action' are required"
assert response["status"] == "0"
assert Map.has_key?(response, "result")
refute response["result"]
end
test "with a valid request", %{conn: conn} do
conn = %Conn{conn | params: %{"module" => "test", "action" => "test_action"}}
result = RPCTranslator.call(conn, %{"test" => TestController})
assert json_response(result, 200) == %{}
end
end
test "translate_module/2" do
assert RPCTranslator.translate_module(%{"test" => __MODULE__}, "tesT") == {:ok, __MODULE__}
assert RPCTranslator.translate_module(%{}, "test") == :error
end
test "translate_action/1" do
expected = :test_atom
assert RPCTranslator.translate_action("test_atoM") == {:ok, expected}
assert RPCTranslator.translate_action("some_atom_that_should_not_exist") == :error
end
test "call_controller/3", %{conn: conn} do
assert RPCTranslator.call_controller(conn, TestController, :bad_action) == :error
assert {:ok, %Plug.Conn{}} = RPCTranslator.call_controller(conn, TestController, :test_action)
end
end
Loading…
Cancel
Save