commit
f56e93f9c5
@ -0,0 +1,14 @@ |
||||
defmodule BlockScoutWeb.Plug.AllowIframe do |
||||
@moduledoc """ |
||||
Allows for iframes by deleting the |
||||
[`X-Frame-Options` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) |
||||
""" |
||||
|
||||
alias Plug.Conn |
||||
|
||||
def init(opts), do: opts |
||||
|
||||
def call(conn, _opts) do |
||||
Conn.delete_resp_header(conn, "x-frame-options") |
||||
end |
||||
end |
@ -0,0 +1,24 @@ |
||||
defmodule BlockScoutWeb.Resolvers.TokenTransfer do |
||||
@moduledoc false |
||||
|
||||
alias Absinthe.Relay.Connection |
||||
alias Explorer.{GraphQL, Repo} |
||||
|
||||
def get_by(%{transaction_hash: _, log_index: _} = args) do |
||||
GraphQL.get_token_transfer(args) |
||||
end |
||||
|
||||
def get_by(_, %{token_contract_address_hash: token_contract_address_hash} = args, _) do |
||||
connection_args = Map.take(args, [:after, :before, :first, :last]) |
||||
|
||||
token_contract_address_hash |
||||
|> GraphQL.list_token_transfers_query() |
||||
|> Connection.from_query(&Repo.all/1, connection_args, options(args)) |
||||
end |
||||
|
||||
defp options(%{before: _}), do: [] |
||||
|
||||
defp options(%{count: count}), do: [count: count] |
||||
|
||||
defp options(_), do: [] |
||||
end |
@ -0,0 +1,326 @@ |
||||
defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do |
||||
use BlockScoutWeb.ConnCase |
||||
|
||||
describe "token_transfers field" do |
||||
test "with valid argument, returns all expected fields", %{conn: conn} do |
||||
transaction = insert(:transaction) |
||||
token_transfer = insert(:token_transfer, transaction: transaction) |
||||
address_hash = to_string(token_transfer.token_contract_address_hash) |
||||
|
||||
query = """ |
||||
query ($token_contract_address_hash: AddressHash!, $first: Int!) { |
||||
token_transfers(token_contract_address_hash: $token_contract_address_hash, first: $first) { |
||||
edges { |
||||
node { |
||||
amount |
||||
block_number |
||||
log_index |
||||
token_id |
||||
from_address_hash |
||||
to_address_hash |
||||
token_contract_address_hash |
||||
transaction_hash |
||||
} |
||||
} |
||||
} |
||||
} |
||||
""" |
||||
|
||||
variables = %{ |
||||
"token_contract_address_hash" => address_hash, |
||||
"first" => 1 |
||||
} |
||||
|
||||
conn = get(conn, "/graphql", query: query, variables: variables) |
||||
|
||||
assert json_response(conn, 200) == %{ |
||||
"data" => %{ |
||||
"token_transfers" => %{ |
||||
"edges" => [ |
||||
%{ |
||||
"node" => %{ |
||||
"amount" => to_string(token_transfer.amount), |
||||
"block_number" => token_transfer.block_number, |
||||
"log_index" => token_transfer.log_index, |
||||
"token_id" => token_transfer.token_id, |
||||
"from_address_hash" => to_string(token_transfer.from_address_hash), |
||||
"to_address_hash" => to_string(token_transfer.to_address_hash), |
||||
"token_contract_address_hash" => to_string(token_transfer.token_contract_address_hash), |
||||
"transaction_hash" => to_string(token_transfer.transaction_hash) |
||||
} |
||||
} |
||||
] |
||||
} |
||||
} |
||||
} |
||||
end |
||||
|
||||
test "with token contract address with zero token transfers", %{conn: conn} do |
||||
address = insert(:contract_address) |
||||
|
||||
query = """ |
||||
query ($token_contract_address_hash: AddressHash!, $first: Int!) { |
||||
token_transfers(token_contract_address_hash: $token_contract_address_hash, first: $first) { |
||||
edges { |
||||
node { |
||||
amount |
||||
block_number |
||||
log_index |
||||
token_id |
||||
from_address_hash |
||||
to_address_hash |
||||
token_contract_address_hash |
||||
transaction_hash |
||||
} |
||||
} |
||||
} |
||||
} |
||||
""" |
||||
|
||||
variables = %{ |
||||
"token_contract_address_hash" => to_string(address.hash), |
||||
"first" => 10 |
||||
} |
||||
|
||||
conn = get(conn, "/graphql", query: query, variables: variables) |
||||
|
||||
assert json_response(conn, 200) == %{ |
||||
"data" => %{ |
||||
"token_transfers" => %{ |
||||
"edges" => [] |
||||
} |
||||
} |
||||
} |
||||
end |
||||
|
||||
test "complexity correlates to first or last argument", %{conn: conn} do |
||||
address = insert(:contract_address) |
||||
|
||||
query1 = """ |
||||
query ($token_contract_address_hash: AddressHash!, $first: Int!) { |
||||
token_transfers(token_contract_address_hash: $token_contract_address_hash, first: $first) { |
||||
edges { |
||||
node { |
||||
amount |
||||
from_address_hash |
||||
to_address_hash |
||||
} |
||||
} |
||||
} |
||||
} |
||||
""" |
||||
|
||||
variables1 = %{ |
||||
"token_contract_address_hash" => to_string(address.hash), |
||||
"first" => 55 |
||||
} |
||||
|
||||
response1 = |
||||
conn |
||||
|> get("/graphql", query: query1, variables: variables1) |
||||
|> json_response(200) |
||||
|
||||
%{"errors" => [response1_error1, response1_error2]} = response1 |
||||
|
||||
assert response1_error1["message"] =~ ~s(Field token_transfers is too complex) |
||||
assert response1_error2["message"] =~ ~s(Operation is too complex) |
||||
|
||||
query2 = """ |
||||
query ($token_contract_address_hash: AddressHash!, $last: Int!) { |
||||
token_transfers(token_contract_address_hash: $token_contract_address_hash, last: $last) { |
||||
edges { |
||||
node { |
||||
amount |
||||
from_address_hash |
||||
to_address_hash |
||||
} |
||||
} |
||||
} |
||||
} |
||||
""" |
||||
|
||||
variables2 = %{ |
||||
"token_contract_address_hash" => to_string(address.hash), |
||||
"last" => 55 |
||||
} |
||||
|
||||
response2 = |
||||
conn |
||||
|> get("/graphql", query: query2, variables: variables2) |
||||
|> json_response(200) |
||||
|
||||
%{"errors" => [response2_error1, response2_error2]} = response2 |
||||
assert response2_error1["message"] =~ ~s(Field token_transfers is too complex) |
||||
assert response2_error2["message"] =~ ~s(Operation is too complex) |
||||
end |
||||
|
||||
test "with 'last' and 'count' arguments", %{conn: conn} do |
||||
# "`last: N` must always be acompanied by either a `before:` argument to |
||||
# the query, or an explicit `count:` option to the `from_query` call. |
||||
# Otherwise it is impossible to derive the required offset." |
||||
# https://hexdocs.pm/absinthe_relay/Absinthe.Relay.Connection.html#from_query/4 |
||||
# |
||||
# This test ensures support for a 'count' argument. |
||||
|
||||
address = insert(:contract_address) |
||||
|
||||
blocks = insert_list(2, :block) |
||||
|
||||
[transaction1, transaction2] = |
||||
for block <- blocks do |
||||
:transaction |
||||
|> insert() |
||||
|> with_block(block) |
||||
end |
||||
|
||||
token_transfer_attrs1 = %{ |
||||
block_number: transaction1.block_number, |
||||
transaction: transaction1, |
||||
token_contract_address: address |
||||
} |
||||
|
||||
token_transfer_attrs2 = %{ |
||||
block_number: transaction2.block_number, |
||||
transaction: transaction2, |
||||
token_contract_address: address |
||||
} |
||||
|
||||
insert(:token_transfer, token_transfer_attrs1) |
||||
insert(:token_transfer, token_transfer_attrs2) |
||||
|
||||
query = """ |
||||
query ($token_contract_address_hash: AddressHash!, $last: Int!, $count: Int) { |
||||
token_transfers(token_contract_address_hash: $token_contract_address_hash, last: $last, count: $count) { |
||||
edges { |
||||
node { |
||||
transaction_hash |
||||
} |
||||
} |
||||
} |
||||
} |
||||
""" |
||||
|
||||
variables = %{ |
||||
"token_contract_address_hash" => to_string(address.hash), |
||||
"last" => 1, |
||||
"count" => 2 |
||||
} |
||||
|
||||
[token_transfer] = |
||||
conn |
||||
|> get("/graphql", query: query, variables: variables) |
||||
|> json_response(200) |
||||
|> get_in(["data", "token_transfers", "edges"]) |
||||
|
||||
assert token_transfer["node"]["transaction_hash"] == to_string(transaction1.hash) |
||||
end |
||||
|
||||
test "pagination support with 'first' and 'after' arguments", %{conn: conn} do |
||||
address = insert(:contract_address) |
||||
|
||||
blocks = insert_list(3, :block) |
||||
|
||||
[transaction1, transaction2, transaction3] = |
||||
transactions = |
||||
for block <- blocks do |
||||
:transaction |
||||
|> insert() |
||||
|> with_block(block) |
||||
end |
||||
|
||||
for transaction <- transactions do |
||||
token_transfer_attrs = %{ |
||||
block_number: transaction.block_number, |
||||
transaction: transaction, |
||||
token_contract_address: address |
||||
} |
||||
|
||||
insert(:token_transfer, token_transfer_attrs) |
||||
end |
||||
|
||||
query1 = """ |
||||
query ($token_contract_address_hash: AddressHash!, $first: Int!) { |
||||
token_transfers(token_contract_address_hash: $token_contract_address_hash, first: $first) { |
||||
page_info { |
||||
has_next_page |
||||
has_previous_page |
||||
} |
||||
edges { |
||||
node { |
||||
transaction_hash |
||||
} |
||||
cursor |
||||
} |
||||
} |
||||
} |
||||
""" |
||||
|
||||
variables1 = %{ |
||||
"token_contract_address_hash" => to_string(address.hash), |
||||
"first" => 1 |
||||
} |
||||
|
||||
conn = get(conn, "/graphql", query: query1, variables: variables1) |
||||
|
||||
%{"data" => %{"token_transfers" => page1}} = json_response(conn, 200) |
||||
|
||||
assert page1["page_info"] == %{"has_next_page" => true, "has_previous_page" => false} |
||||
assert Enum.all?(page1["edges"], &(&1["node"]["transaction_hash"] == to_string(transaction3.hash))) |
||||
|
||||
last_cursor_page1 = |
||||
page1 |
||||
|> Map.get("edges") |
||||
|> List.last() |
||||
|> Map.get("cursor") |
||||
|
||||
query2 = """ |
||||
query ($token_contract_address_hash: AddressHash!, $first: Int!, $after: ID!) { |
||||
token_transfers(token_contract_address_hash: $token_contract_address_hash, first: $first, after: $after) { |
||||
page_info { |
||||
has_next_page |
||||
has_previous_page |
||||
} |
||||
edges { |
||||
node { |
||||
transaction_hash |
||||
} |
||||
cursor |
||||
} |
||||
} |
||||
} |
||||
""" |
||||
|
||||
variables2 = %{ |
||||
"token_contract_address_hash" => to_string(address.hash), |
||||
"first" => 1, |
||||
"after" => last_cursor_page1 |
||||
} |
||||
|
||||
conn = get(conn, "/graphql", query: query2, variables: variables2) |
||||
|
||||
%{"data" => %{"token_transfers" => page2}} = json_response(conn, 200) |
||||
|
||||
assert page2["page_info"] == %{"has_next_page" => true, "has_previous_page" => true} |
||||
assert Enum.all?(page2["edges"], &(&1["node"]["transaction_hash"] == to_string(transaction2.hash))) |
||||
|
||||
last_cursor_page2 = |
||||
page2 |
||||
|> Map.get("edges") |
||||
|> List.last() |
||||
|> Map.get("cursor") |
||||
|
||||
variables3 = %{ |
||||
"token_contract_address_hash" => to_string(address.hash), |
||||
"first" => 1, |
||||
"after" => last_cursor_page2 |
||||
} |
||||
|
||||
conn = get(conn, "/graphql", query: query2, variables: variables3) |
||||
|
||||
%{"data" => %{"token_transfers" => page3}} = json_response(conn, 200) |
||||
|
||||
assert page3["page_info"] == %{"has_next_page" => false, "has_previous_page" => true} |
||||
assert Enum.all?(page3["edges"], &(&1["node"]["transaction_hash"] == to_string(transaction1.hash))) |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue