Skip to content

Commit

Permalink
test: add helper with_state/2 function for declarative tests (#796)
Browse files Browse the repository at this point in the history
This not only avoids mocking every single Database function, but also
allows for declarative and functional tests by specifying the state of
the store required for the test to pass.
  • Loading branch information
sborrazas committed Jul 20, 2022
1 parent e6f0366 commit c57056a
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 46 deletions.
2 changes: 2 additions & 0 deletions lib/ae_mdw_web/plugs/state_plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ defmodule AeMdwWeb.Plugs.StatePlug do
def init(opts), do: opts

@spec call(Conn.t(), Plug.opts()) :: Conn.t()
def call(%Conn{assigns: %{state: _state}} = conn, _opts), do: conn

def call(conn, _opts), do: Conn.assign(conn, :state, State.mem_state())
end
93 changes: 54 additions & 39 deletions test/ae_mdw_web/controllers/oracle_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ defmodule AeMdwWeb.OracleControllerTest do

require AeMdw.Db.Model

alias :aeser_api_encoder, as: Enc
alias AeMdw.Blocks
alias AeMdw.Db.Model
alias AeMdw.Db.Model.ActiveOracleExpiration
alias AeMdw.Db.Model.InactiveOracleExpiration
alias AeMdw.Db.Model.Block
alias AeMdw.Db.Oracle
alias AeMdw.Db.Store
alias AeMdw.Database
alias AeMdw.TestSamples, as: TS

Expand All @@ -33,7 +35,7 @@ defmodule AeMdwWeb.OracleControllerTest do
end,
get: fn
Model.ActiveOracle, _pk -> {:ok, oracle}
Model.Tx, _txi -> {:ok, Model.tx(id: TS.tx_hash())}
Model.Tx, _txi -> {:ok, Model.tx(id: TS.tx_hash(0))}
end
]},
{Oracle, [], [oracle_tree!: fn _block_hash -> :aeo_state_tree.empty() end]},
Expand Down Expand Up @@ -77,7 +79,7 @@ defmodule AeMdwWeb.OracleControllerTest do
get: fn
Model.InactiveOracle, _oracle_pk -> {:ok, oracle}
Model.ActiveOracle, _oracle_pk -> {:ok, oracle}
Model.Tx, _txi -> {:ok, Model.tx(id: TS.tx_hash())}
Model.Tx, _txi -> {:ok, Model.tx(id: TS.tx_hash(0))}
end
]},
{Oracle, [], [oracle_tree!: fn _block_hash -> :aeo_state_tree.empty() end]},
Expand All @@ -93,48 +95,61 @@ defmodule AeMdwWeb.OracleControllerTest do
end
end

test "it displays tx hashes when tx_hash=true", %{conn: conn} do
Model.oracle(extends: [{_extends_bi, extends_txi}]) = oracle = TS.oracle()
tx_hash = TS.tx_hash()
test "it displays tx hashes when tx_hash=true", %{conn: conn, store: store} do
register_tx1 = Model.tx(index: register_txi1, id: register_tx_hash1) = TS.tx(0)
register_tx2 = Model.tx(index: register_txi2, id: register_tx_hash2) = TS.tx(1)
extends_tx = Model.tx(index: extends_txi, id: extends_tx_hash) = TS.tx(2)
oracle_exp1 = {exp_height1, oracle_pk1} = TS.oracle_expiration_key(0)
oracle_exp2 = {exp_height2, oracle_pk2} = TS.oracle_expiration_key(1)

oracle1 =
Model.oracle(TS.oracle(),
index: oracle_pk1,
register: {{0, -1}, register_txi1},
expire: exp_height1,
extends: [{{0, -1}, extends_txi}]
)

oracle2 =
Model.oracle(TS.oracle(),
index: oracle_pk2,
register: {{0, -1}, register_txi2},
expire: exp_height2,
extends: []
)

block_hash = TS.key_block_hash(0)

store =
store
|> Store.put(Model.Tx, register_tx1)
|> Store.put(Model.Tx, register_tx2)
|> Store.put(Model.Tx, extends_tx)
|> Store.put(Model.ActiveOracleExpiration, Model.expiration(index: oracle_exp1))
|> Store.put(Model.ActiveOracleExpiration, Model.expiration(index: oracle_exp2))
|> Store.put(Model.ActiveOracle, oracle1)
|> Store.put(Model.ActiveOracle, oracle2)
|> Store.put(Model.Block, Model.block(index: {exp_height1, -1}, hash: block_hash))
|> Store.put(Model.Block, Model.block(index: {exp_height2, -1}, hash: block_hash))
|> Store.put(Model.Block, Model.block(index: {exp_height1 - 1, -1}, hash: block_hash))
|> Store.put(Model.Block, Model.block(index: {exp_height2 - 1, -1}, hash: block_hash))

with_mocks [
{Database, [],
[
next_key: fn _tab, _key -> :none end,
prev_key: fn
ActiveOracleExpiration, {0, _plain_name} -> :none
ActiveOracleExpiration, {exp, "a"} -> {:ok, {exp - 1, "a"}}
_tab, _key -> :none
end,
last_key: fn
Block -> {:ok, TS.last_gen()}
ActiveOracleExpiration -> {:ok, {1, "a"}}
InactiveOracleExpiration -> :none
end,
get: fn
Model.ActiveOracle, _oracle_pk -> {:ok, oracle}
Model.Tx, _txi -> {:ok, Model.tx(id: tx_hash)}
end
]},
{Oracle, [], [oracle_tree!: fn _block_hash -> :aeo_state_tree.empty() end]},
{:aeo_state_tree, [:passthrough], [get_oracle: fn _pk, _tree -> TS.core_oracle() end]},
{Blocks, [], [block_hash: fn _state, _height -> "asd" end]}
{Oracle, [], [oracle_tree!: fn ^block_hash -> :aeo_state_tree.empty() end]},
{:aeo_state_tree, [:passthrough], [get_oracle: fn _pk, _tree -> TS.core_oracle() end]}
] do
assert %{"data" => oracles, "next" => nil} =
assert %{"data" => [oracle2, oracle1], "next" => nil} =
conn
|> get("/oracles", tx_hash: "true")
|> with_store(store)
|> get("/oracles", tx_hash: "true", limit: 2)
|> json_response(200)

assert Enum.all?(oracles, fn %{"extends" => extends} ->
Enum.all?(extends, fn extends_tx_hash ->
match?(
{:ok, ^tx_hash},
:aeser_api_encoder.safe_decode(:tx_hash, extends_tx_hash)
)
end)
end)
assert %{"register_tx_hash" => register_hash, "extends" => [extends_hash]} = oracle1
assert {:ok, ^register_tx_hash1} = Enc.safe_decode(:tx_hash, register_hash)
assert {:ok, ^extends_tx_hash} = Enc.safe_decode(:tx_hash, extends_hash)

assert_called(Database.get(Model.Tx, extends_txi))
assert %{"register_tx_hash" => register_hash, "extends" => []} = oracle2
assert {:ok, ^register_tx_hash2} = Enc.safe_decode(:tx_hash, register_hash)
end
end

Expand Down Expand Up @@ -162,7 +177,7 @@ defmodule AeMdwWeb.OracleControllerTest do
end,
get: fn
Model.ActiveOracle, _oracle_pk -> {:ok, oracle}
Model.Tx, _txi -> {:ok, Model.tx(id: TS.tx_hash())}
Model.Tx, _txi -> {:ok, Model.tx(id: TS.tx_hash(0))}
end,
next_key: fn _tab, _key -> :none end,
prev_key: fn
Expand Down Expand Up @@ -201,7 +216,7 @@ defmodule AeMdwWeb.OracleControllerTest do
prev_key: fn ActiveOracleExpiration, _key -> {:ok, expiration_key} end,
get: fn
Model.ActiveOracle, _oracle_pk -> {:ok, TS.oracle()}
Model.Tx, _txi -> {:ok, Model.tx(id: TS.tx_hash())}
Model.Tx, _txi -> {:ok, Model.tx(id: TS.tx_hash(0))}
end
]},
{Oracle, [], [oracle_tree!: fn _block_hash -> :aeo_state_tree.empty() end]},
Expand Down
8 changes: 7 additions & 1 deletion test/support/conn_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ defmodule AeMdwWeb.ConnCase do

use ExUnit.CaseTemplate

alias Phoenix.ConnTest

using do
quote do
# Import conveniences for testing with connections
import Plug.Conn
import Phoenix.ConnTest
import AeMdwWeb.TestUtil
alias AeMdwWeb.Router.Helpers, as: Routes

# The default endpoint for testing
Expand All @@ -30,6 +33,9 @@ defmodule AeMdwWeb.ConnCase do
end

setup _tags do
{:ok, conn: Phoenix.ConnTest.build_conn()}
alias AeMdw.Db.MemStore
alias AeMdw.Db.NullStore

{:ok, conn: ConnTest.build_conn(), store: MemStore.new(NullStore.new())}
end
end
42 changes: 36 additions & 6 deletions test/support/test_sample.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule AeMdw.TestSamples do
@spec oracle_expiration_key(non_neg_integer()) :: binary()
def oracle_expiration_key(n) do
~w(
ok_2TASQ4QZv584D2ZP7cZxT6sk1L1UyqbWumnWM4g1azGi1qqcR5
ok_24jcHLTZQfsou7NvomRJ1hKEnjyNqbYSq2Az7DmyrAyUHPq8uR
ok_R7cQfVN15F5ek1wBSYaMRjW2XbMRKx7VDQQmbtwxspjZQvmPM
ok_g5vQK6beY3vsTJHH7KBusesyzq9WMdEYorF8VyvZURXTjLnxT
ok_pANDBzM259a9UgZFeiCJyWjXSeRhqrBQ6UCBBeXfbCQyP33Tf
Expand Down Expand Up @@ -62,11 +62,41 @@ defmodule AeMdw.TestSamples do
2_000_000_000_000_000, 288_692, 0}
end

@spec tx_hash() :: Txs.tx_hash()
def tx_hash do
{:tx_hash, hash} =
:aeser_api_encoder.decode("th_5aUc7Xqk3TkG38XVjCzZPAWc1geGFfjGcgEytcZAFe3FJyn4g")
@spec tx_hash(non_neg_integer()) :: Txs.tx_hash()
def tx_hash(n) do
~w(
th_2MMJRvBUj69PHoZhQtrrXHAg5iXCVwPcunXrKaPcVB6yhAuUHo
th_uJ5os7Gg8P68SHTq1kYecNzjNPFp3exxhXvqWmpFfsS7mbKSG
th_2CdVYuqtpcjoshDw2otjbLEyj8SpxjMP9MCgpn1oU9zGaqvUn4
th_2oBM2J4CtvXyg3ZNKdwbhskjQXTGn6AHA8iZqiYB2YEZjbjPP1
th_2gyafjpuMFK16ZcJjp4ss5GtENJYZjBtgMxc5E6xguaT2TqHpy
)
|> Enum.map(fn tx_hash ->
{:ok, hash} = :aeser_api_encoder.safe_decode(:tx_hash, tx_hash)

hash
hash
end)
|> Enum.at(n)
end

@spec tx(non_neg_integer()) :: Model.tx()
def tx(n) do
Model.tx(index: n * 200, id: tx_hash(n))
end

@spec key_block_hash(non_neg_integer()) :: Blocks.block_hash()
def key_block_hash(n) do
~w(
kh_2f4aqtJjJpNdgYMsvXJ8E8XECNSJPfkj3B482yfZ5wAXN6aJAT
kh_2gfAhphwc1nqdqmpmzewqKDNkwfgBDrF2zJ7PBzt2nujjBevHg
kh_EQvXNAZLUtA98FeSUKjwuusuoMvHBPUMAJ4rDBuRBs5mWbvxs
kh_p5Ah5sKuGd3B4Z6Md4wwXvv1C4DBuNEWovraEVefbyCTtYUfU
)
|> Enum.map(fn hash ->
{:ok, decoded_hash} = :aeser_api_encoder.safe_decode(:key_block_hash, hash)

decoded_hash
end)
|> Enum.at(n)
end
end
12 changes: 12 additions & 0 deletions test/support/test_util.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
defmodule AeMdwWeb.TestUtil do
@moduledoc """
Test helper funcitons imported by default on all tests.
"""

alias AeMdw.Error.Input, as: ErrInput
alias AeMdw.Db.State
alias Plug.Conn

@spec handle_input((() -> Conn.t())) :: Conn.t() | String.t()
def handle_input(f) do
try do
f.()
Expand All @@ -9,4 +16,9 @@ defmodule AeMdwWeb.TestUtil do
err.message
end
end

@spec with_store(Conn.t(), Store.t()) :: Conn.t()
def with_store(conn, store) do
Conn.assign(conn, :state, State.new(store))
end
end

0 comments on commit c57056a

Please sign in to comment.